Skip to content

Commit

Permalink
Added activity item for fleetd enrollment with host serial and displa…
Browse files Browse the repository at this point in the history
…y name. (#23790)

#22810 

# Demo
[![22810
demo](http://img.youtube.com/vi/le71QQ92suc/0.jpg)](http://www.youtube.com/watch?v=le71QQ92suc)

# Checklist for submitter

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files)
for more information.
- [x] Added support on fleet's osquery simulator `cmd/osquery-perf` for
new osquery data ingestion features.
- [x] Added/updated tests
- [x] Manual QA for all new/changed functionality
- For Orbit and Fleet Desktop changes:
- [x] Orbit runs on macOS, Linux and Windows. Check if the orbit
feature/bugfix should only apply to one platform (`runtime.GOOS`).
- [x] Manual QA must be performed in the three main OSs, macOS, Windows
and Linux.
- [x] Auto-update manual QA, from released version of component to new
version (see [tools/tuf/test](../tools/tuf/test/README.md)).
  • Loading branch information
getvictor authored Nov 18, 2024
1 parent 18f6011 commit 698e9e8
Show file tree
Hide file tree
Showing 14 changed files with 160 additions and 17 deletions.
1 change: 1 addition & 0 deletions changes/22810-fleetd-enroll-activity
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added activity item for fleetd enrollment with host serial and display name.
1 change: 1 addition & 0 deletions cmd/osquery-perf/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -1413,6 +1413,7 @@ func (a *agent) orbitEnroll() error {
EnrollSecret: a.EnrollSecret,
HardwareUUID: a.UUID,
HardwareSerial: a.SerialNumber,
Hostname: a.CachedString("hostname"),
}
jsonBytes, err := json.Marshal(params)
if err != nil {
Expand Down
17 changes: 17 additions & 0 deletions docs/Contributing/Audit-logs.md
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,23 @@ This activity contains the following fields:
}
```

## fleet_enrolled

Generated when a host is enrolled to Fleet (Fleet's agent fleetd is installed).

This activity contains the following fields:
- "host_serial": Serial number of the host.
- "host_display_name": Display name of the host.

#### Example

```json
{
"host_serial": "B04FL3ALPT21",
"host_display_name": "WIN-DESKTOP-JGS78KJ7C"
}
```

## mdm_enrolled

Generated when a host is enrolled in Fleet's MDM.
Expand Down
1 change: 1 addition & 0 deletions frontend/interfaces/activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export enum ActivityType {
UserDeletedGlobalRole = "deleted_user_global_role",
UserChangedTeamRole = "changed_user_team_role",
UserDeletedTeamRole = "deleted_user_team_role",
FleetEnrolled = "fleet_enrolled",
MdmEnrolled = "mdm_enrolled",
MdmUnenrolled = "mdm_unenrolled",
EditedMacosMinVersion = "edited_macos_min_version",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,14 @@ const TAGGED_TEMPLATES = {
</>
);
},
fleetEnrolled: (activity: IActivity) => {
const hostDisplayName = activity.details?.host_display_name ? (
<b>{activity.details.host_display_name}</b>
) : (
"A host"
);
return <>{hostDisplayName} enrolled in Fleet.</>;
},
mdmEnrolled: (activity: IActivity) => {
if (activity.details?.mdm_platform === "microsoft") {
return (
Expand Down Expand Up @@ -1167,6 +1175,9 @@ const getDetail = (
case ActivityType.UserDeletedTeamRole: {
return TAGGED_TEMPLATES.userDeletedTeamRole(activity);
}
case ActivityType.FleetEnrolled: {
return TAGGED_TEMPLATES.fleetEnrolled(activity);
}
case ActivityType.MdmEnrolled: {
return TAGGED_TEMPLATES.mdmEnrolled(activity);
}
Expand Down
2 changes: 2 additions & 0 deletions orbit/changes/22810-fleetd-enroll-activity
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Added computer_name and hardware_model for fleetd enrollment.
Added serial number for fleetd enrollment for Windows hosts (already present for macOS and Linux).
26 changes: 18 additions & 8 deletions orbit/cmd/orbit/orbit.go
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,8 @@ func main() {
HardwareUUID: osqueryHostInfo.HardwareUUID,
Hostname: osqueryHostInfo.Hostname,
Platform: osqueryHostInfo.Platform,
ComputerName: osqueryHostInfo.ComputerName,
HardwareModel: osqueryHostInfo.HardwareModel,
}

if runtime.GOOS == "darwin" {
Expand Down Expand Up @@ -737,13 +739,6 @@ func main() {
orbitHostInfo.OsqueryIdentifier = osqueryHostInfo.InstanceID
}

// The hardware serial was not sent when Windows MDM was implemented,
// thus we clear its value here to not break any existing enroll functionality
// on the server.
if runtime.GOOS == "windows" {
orbitHostInfo.HardwareSerial = ""
}

var (
options []osquery.Option
// optionsAfterFlagfile is populated with options that will be set after the '--flagfile' argument
Expand Down Expand Up @@ -1697,6 +1692,10 @@ type osqueryHostInfo struct {
HardwareSerial string `json:"hardware_serial"`
// Hostname is the device's hostname (extracted from `system_info` osquery table).
Hostname string `json:"hostname"`
// ComputerName is the friendly computer name (optional) (extracted from `system_info` osquery table).
ComputerName string `json:"computer_name"`
// HardwareModel is the device's hardware model (extracted from `system_info` osquery table).
HardwareModel string `json:"hardware_model"`
// Platform is the device's platform as defined by osquery (extracted from `os_version` osquery table).
Platform string `json:"platform"`
// InstanceID is the osquery's randomly generated instance ID
Expand All @@ -1714,7 +1713,18 @@ func getHostInfo(osqueryPath string, osqueryDBPath string) (*osqueryHostInfo, er
if err := os.MkdirAll(filepath.Dir(osqueryDBPath), constant.DefaultDirMode); err != nil {
return nil, err
}
const systemQuery = "SELECT si.uuid, si.hardware_serial, si.hostname, os.platform, os.version as os_version, oi.instance_id, oi.version as osquery_version FROM system_info si, os_version os, osquery_info oi"
const systemQuery = `
SELECT
si.uuid,
si.hardware_serial,
si.hostname,
si.computer_name,
si.hardware_model,
os.platform,
os.version as os_version,
oi.instance_id,
oi.version as osquery_version
FROM system_info si, os_version os, osquery_info oi`
args := []string{
"-S",
"--database_path", osqueryDBPath,
Expand Down
29 changes: 24 additions & 5 deletions server/datastore/mysql/hosts.go
Original file line number Diff line number Diff line change
Expand Up @@ -1904,9 +1904,20 @@ func (ds *Datastore) EnrollOrbit(ctx context.Context, isMDMEnabled bool, hostInf
}
// NOTE: allow an empty serial, currently it is empty for Windows.

var host fleet.Host
host := fleet.Host{
ComputerName: hostInfo.ComputerName,
Hostname: hostInfo.Hostname,
HardwareModel: hostInfo.HardwareModel,
HardwareSerial: hostInfo.HardwareSerial,
}
err := ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
enrolledHostInfo, err := matchHostDuringEnrollment(ctx, tx, orbitEnroll, isMDMEnabled, hostInfo.OsqueryIdentifier, hostInfo.HardwareUUID, hostInfo.HardwareSerial)
serialToMatch := hostInfo.HardwareSerial
if hostInfo.Platform == "windows" {
// For Windows, don't match by serial number to retain legacy functionality.
serialToMatch = ""
}
enrolledHostInfo, err := matchHostDuringEnrollment(ctx, tx, orbitEnroll, isMDMEnabled, hostInfo.OsqueryIdentifier,
hostInfo.HardwareUUID, serialToMatch)

// If the osquery identifier that osqueryd will use was not sent by Orbit, then use the hardware UUID as identifier
// (using the hardware UUID is Orbit's default behavior).
Expand Down Expand Up @@ -1936,13 +1947,17 @@ func (ds *Datastore) EnrollOrbit(ctx context.Context, isMDMEnabled bool, hostInf
uuid = COALESCE(NULLIF(uuid, ''), ?),
osquery_host_id = COALESCE(NULLIF(osquery_host_id, ''), ?),
hardware_serial = COALESCE(NULLIF(hardware_serial, ''), ?),
computer_name = COALESCE(NULLIF(computer_name, ''), ?),
hardware_model = COALESCE(NULLIF(hardware_model, ''), ?),
team_id = ?
WHERE id = ?`
_, err := tx.ExecContext(ctx, sqlUpdate,
orbitNodeKey,
hostInfo.HardwareUUID,
osqueryIdentifier,
hostInfo.HardwareSerial,
hostInfo.ComputerName,
hostInfo.HardwareModel,
teamID,
enrolledHostInfo.ID,
)
Expand Down Expand Up @@ -1977,8 +1992,10 @@ func (ds *Datastore) EnrollOrbit(ctx context.Context, isMDMEnabled bool, hostInf
orbit_node_key,
hardware_serial,
hostname,
computer_name,
hardware_model,
platform
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?)
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)
`
result, err := tx.ExecContext(ctx, sqlInsert,
zeroTime,
Expand All @@ -1992,16 +2009,18 @@ func (ds *Datastore) EnrollOrbit(ctx context.Context, isMDMEnabled bool, hostInf
orbitNodeKey,
hostInfo.HardwareSerial,
hostInfo.Hostname,
hostInfo.ComputerName,
hostInfo.HardwareModel,
hostInfo.Platform,
)
if err != nil {
return ctxerr.Wrap(ctx, err, "orbit enroll error inserting host details")
}
hostID, _ := result.LastInsertId()
const sqlHostDisplayName = `
INSERT INTO host_display_names (host_id, display_name) VALUES (?, '')
INSERT INTO host_display_names (host_id, display_name) VALUES (?, ?)
`
_, err = tx.ExecContext(ctx, sqlHostDisplayName, hostID)
_, err = tx.ExecContext(ctx, sqlHostDisplayName, hostID, host.DisplayName())
if err != nil {
return ctxerr.Wrap(ctx, err, "insert host_display_names")
}
Expand Down
28 changes: 25 additions & 3 deletions server/datastore/mysql/hosts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8061,6 +8061,11 @@ func testHostsGetUnverifiedDiskEncryptionKeys(t *testing.T, ds *Datastore) {
func testHostsEnrollOrbit(t *testing.T, ds *Datastore) {
ctx := context.Background()

const (
computerName = "My computer"
hardwareModel = "CMP-1000"
)

createHost := func(osqueryID, serial string) *fleet.Host {
dbZeroTime := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
var osqueryIDPtr *string
Expand All @@ -8075,6 +8080,8 @@ func testHostsEnrollOrbit(t *testing.T, ds *Datastore) {
DetailUpdatedAt: dbZeroTime,
OsqueryHostID: osqueryIDPtr,
RefetchRequested: true,
ComputerName: computerName,
HardwareModel: hardwareModel,
})
require.NoError(t, err)
return h
Expand Down Expand Up @@ -8112,10 +8119,19 @@ func testHostsEnrollOrbit(t *testing.T, ds *Datastore) {
h, err = ds.EnrollOrbit(ctx, true, fleet.OrbitHostInfo{
HardwareUUID: *hBoth.OsqueryHostID,
HardwareSerial: hBoth.HardwareSerial,
ComputerName: hBoth.ComputerName,
HardwareModel: hBoth.HardwareModel,
}, uuid.New().String(), nil)
require.NoError(t, err)
require.Equal(t, hBoth.ID, h.ID)
require.Empty(t, h.HardwareSerial) // this is just to prove that it was loaded based on osquery_host_id, the serial was not set in the lookup
assert.Equal(t, hBoth.HardwareSerial, h.HardwareSerial)
assert.Equal(t, hBoth.ComputerName, h.ComputerName)
assert.Equal(t, hBoth.HardwareModel, h.HardwareModel)
h, err = ds.Host(ctx, h.ID)
require.NoError(t, err)
assert.Equal(t, hBoth.HardwareSerial, h.HardwareSerial)
assert.Equal(t, hBoth.ComputerName, h.ComputerName)
assert.Equal(t, hBoth.HardwareModel, h.HardwareModel)

// enroll with osquery id from hBoth and serial from hSerialNoOsquery (should
// use the osquery match)
Expand All @@ -8125,14 +8141,17 @@ func testHostsEnrollOrbit(t *testing.T, ds *Datastore) {
}, uuid.New().String(), nil)
require.NoError(t, err)
require.Equal(t, hBoth.ID, h.ID)
require.Empty(t, h.HardwareSerial)
assert.Equal(t, hSerialNoOsquery.HardwareSerial, h.HardwareSerial)

// enroll with no match, will create a new one
newSerial := uuid.NewString()
h, err = ds.EnrollOrbit(ctx, true, fleet.OrbitHostInfo{
HardwareUUID: uuid.New().String(),
HardwareSerial: uuid.New().String(),
HardwareSerial: newSerial,
Hostname: "foo2",
Platform: "darwin",
ComputerName: "New computer",
HardwareModel: "ABC-3000",
}, uuid.New().String(), nil)
require.NoError(t, err)
require.Greater(t, h.ID, hBoth.ID)
Expand All @@ -8141,6 +8160,9 @@ func testHostsEnrollOrbit(t *testing.T, ds *Datastore) {
require.NoError(t, err)
require.Equal(t, "foo2", h.Hostname)
require.Equal(t, "darwin", h.Platform)
assert.Equal(t, "New computer", h.ComputerName)
assert.Equal(t, "ABC-3000", h.HardwareModel)
assert.Equal(t, newSerial, h.HardwareSerial)

// simulate a "corrupt database" where two hosts have the same serial and
// enroll by serial should always use the same (the smaller ID)
Expand Down
20 changes: 20 additions & 0 deletions server/fleet/activities.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ var ActivityDetailsList = []ActivityDetails{
ActivityTypeChangedUserTeamRole{},
ActivityTypeDeletedUserTeamRole{},

ActivityTypeFleetEnrolled{},
ActivityTypeMDMEnrolled{},
ActivityTypeMDMUnenrolled{},

Expand Down Expand Up @@ -795,6 +796,25 @@ func (a ActivityTypeDeletedUserTeamRole) Documentation() (activity string, detai
}`
}

type ActivityTypeFleetEnrolled struct {
HostSerial string `json:"host_serial"`
HostDisplayName string `json:"host_display_name"`
}

func (a ActivityTypeFleetEnrolled) ActivityName() string {
return "fleet_enrolled"
}

func (a ActivityTypeFleetEnrolled) Documentation() (activity string, details string, detailsExample string) {
return `Generated when a host is enrolled to Fleet (Fleet's agent fleetd is installed).`,
`This activity contains the following fields:
- "host_serial": Serial number of the host.
- "host_display_name": Display name of the host.`, `{
"host_serial": "B04FL3ALPT21",
"host_display_name": "WIN-DESKTOP-JGS78KJ7C"
}`
}

type ActivityTypeMDMEnrolled struct {
HostSerial string `json:"host_serial"`
HostDisplayName string `json:"host_display_name"`
Expand Down
4 changes: 4 additions & 0 deletions server/fleet/orbit.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ type OrbitHostInfo struct {
//
// If not set, then the HardwareUUID is used/set as the osquery identifier.
OsqueryIdentifier string
// ComputerName is the device's friendly name (optional).
ComputerName string
// HardwareModel is the device's hardware model. For example: Standard PC (Q35 + ICH9, 2009)
HardwareModel string
}

// ExtensionInfo holds the data of a osquery extension to apply to an Orbit client.
Expand Down
14 changes: 14 additions & 0 deletions server/service/integration_mdm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2661,10 +2661,14 @@ func (s *integrationMDMTestSuite) TestEnrollOrbitAfterDEPSync() {
// enroll the host from orbit, it should match the host above via the serial
var resp EnrollOrbitResponse
hostUUID := uuid.New().String()
h.ComputerName = "My Mac"
h.HardwareModel = "MacBook Pro"
s.DoJSON("POST", "/api/fleet/orbit/enroll", EnrollOrbitRequest{
EnrollSecret: secret,
HardwareUUID: hostUUID, // will not match any existing host
HardwareSerial: h.HardwareSerial,
ComputerName: h.ComputerName,
HardwareModel: h.HardwareModel,
}, http.StatusOK, &resp)
require.NotEmpty(t, resp.OrbitNodeKey)

Expand All @@ -2674,11 +2678,21 @@ func (s *integrationMDMTestSuite) TestEnrollOrbitAfterDEPSync() {
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d", h.ID), nil, http.StatusOK, &hostResp)
require.Equal(t, h.ID, hostResp.Host.ID)
require.NotEqual(t, dbZeroTime, hostResp.Host.LastEnrolledAt)
assert.Equal(t, h.ComputerName, hostResp.Host.ComputerName)
assert.Equal(t, h.HardwareModel, hostResp.Host.HardwareModel)
assert.Equal(t, h.HardwareSerial, hostResp.Host.HardwareSerial)
assert.Equal(t, h.DisplayName(), hostResp.Host.DisplayName)

got, err := s.ds.LoadHostByOrbitNodeKey(ctx, resp.OrbitNodeKey)
require.NoError(t, err)
require.Equal(t, h.ID, got.ID)

s.lastActivityMatches(
"fleet_enrolled",
fmt.Sprintf(`{"host_display_name": "%s", "host_serial": "%s"}`, h.DisplayName(), h.HardwareSerial),
0,
)

// enroll the host from osquery, it should match the same host
var osqueryResp enrollAgentResponse
osqueryID := uuid.New().String()
Expand Down
Loading

0 comments on commit 698e9e8

Please sign in to comment.