mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
add activities when a host is enrolled/unenrolled from MDM (#9127)
#8996
This commit is contained in:
parent
aedb0424a2
commit
1b47f9e700
18 changed files with 426 additions and 120 deletions
1
changes/8996-api-activities
Normal file
1
changes/8996-api-activities
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Added new activities to the activities API when a host is enrolled/unenrolled from Fleet's MDM.
|
||||
|
|
@ -35,12 +35,12 @@ func registerAppleMDMProtocolServices(
|
|||
mdmStorage *mysql.NanoMDMStorage,
|
||||
scepStorage *apple_mdm.SCEPMySQLDepot,
|
||||
logger kitlog.Logger,
|
||||
mdmHostIngester fleet.MDMHostIngester,
|
||||
ds fleet.Datastore,
|
||||
) error {
|
||||
if err := registerSCEP(mux, scepConfig, scepCertPEM, scepKeyPEM, scepStorage, logger); err != nil {
|
||||
return fmt.Errorf("scep: %w", err)
|
||||
}
|
||||
if err := registerMDM(mux, scepCertPEM, mdmStorage, logger, mdmHostIngester); err != nil {
|
||||
if err := registerMDM(mux, scepCertPEM, mdmStorage, ds, logger); err != nil {
|
||||
return fmt.Errorf("mdm: %w", err)
|
||||
}
|
||||
return nil
|
||||
|
|
@ -123,8 +123,8 @@ func registerMDM(
|
|||
mux *http.ServeMux,
|
||||
scepCAPEM []byte,
|
||||
mdmStorage *mysql.NanoMDMStorage,
|
||||
ds fleet.Datastore,
|
||||
logger kitlog.Logger,
|
||||
mdmHostIngester fleet.MDMHostIngester,
|
||||
) error {
|
||||
certVerifier, err := certverify.NewPoolVerifier(scepCAPEM, x509.ExtKeyUsageClientAuth)
|
||||
if err != nil {
|
||||
|
|
@ -143,22 +143,23 @@ func registerMDM(
|
|||
var mdmService nanomdm_service.CheckinAndCommandService = nanomdm.New(mdmStorage, nanomdm.WithLogger(mdmLogger))
|
||||
mdmService = certauth.New(mdmService, mdmStorage)
|
||||
var mdmHandler http.Handler = httpmdm.CheckinAndCommandHandler(mdmService, mdmLogger.With("handler", "checkin-command"))
|
||||
mdmHandler = MDMHostIngesterMiddleware(mdmHandler, mdmHostIngester, logger)
|
||||
mdmHandler = MDMCheckinMiddleware(mdmHandler, ds, logger)
|
||||
mdmHandler = httpmdm.CertVerifyMiddleware(mdmHandler, certVerifier, mdmLogger.With("handler", "cert-verify"))
|
||||
mdmHandler = httpmdm.CertExtractMdmSignatureMiddleware(mdmHandler, mdmLogger.With("handler", "cert-extract"))
|
||||
mux.Handle(apple_mdm.MDMPath, mdmHandler)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MDMHostIngesterMiddleware watches incoming requests in order to ingest new Fleet hosts from pending
|
||||
// MDM enrollments. It updates the Fleet hosts table accordingly with the UDID and serial number of
|
||||
// the device.
|
||||
func MDMHostIngesterMiddleware(next http.Handler, ingester fleet.MDMHostIngester, logger kitlog.Logger) http.HandlerFunc {
|
||||
// MDMCheckinMiddleware watches incoming requests in order to
|
||||
// take actions on the different MDM check-in lifecycle
|
||||
// events, this might include enrolling a new host during
|
||||
// Authentication or adding activities on CheckOut.
|
||||
func MDMCheckinMiddleware(next http.Handler, ds fleet.Datastore, logger kitlog.Logger) http.HandlerFunc {
|
||||
logger = kitlog.With(logger, "component", "mdm-apple-host-ingester")
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
if err := ingester.Ingest(ctx, r); err != nil {
|
||||
if err := fleet.HandleMDMCheckinRequest(ctx, r, ds); err != nil {
|
||||
level.Error(logger).Log("err", "ingest checkin request", "details", err)
|
||||
sentry.CaptureException(err)
|
||||
ctxerr.Handle(ctx, err)
|
||||
|
|
|
|||
|
|
@ -756,7 +756,7 @@ the way that the Fleet server works.
|
|||
mdmStorage,
|
||||
scepStorage,
|
||||
logger,
|
||||
fleet.NewMDMAppleHostIngester(ds, logger),
|
||||
ds,
|
||||
); err != nil {
|
||||
initFatal(err, "setup mdm apple services")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -840,7 +840,7 @@ func TestCronActivitiesStreaming(t *testing.T) {
|
|||
jsonRawMessage := json.RawMessage(details)
|
||||
return &fleet.Activity{
|
||||
ID: id,
|
||||
ActorFullName: actorName,
|
||||
ActorFullName: &actorName,
|
||||
ActorID: &actorID,
|
||||
ActorGravatar: &actorGravatar,
|
||||
ActorEmail: &actorEmail,
|
||||
|
|
|
|||
|
|
@ -507,6 +507,40 @@ This activity contains the following fields:
|
|||
}
|
||||
```
|
||||
|
||||
### Type `mdm_enrolled`
|
||||
|
||||
Generated when a host is enrolled in Fleet's MDM.
|
||||
|
||||
This activity contains the following fields:
|
||||
- "host_serial": Serial number of the host.
|
||||
- "installed_from_dep": Whether the host was enrolled via DEP.
|
||||
|
||||
#### Example
|
||||
|
||||
```json
|
||||
{
|
||||
"host_serial": "C08VQ2AXHT96",
|
||||
"installed_from_dep": true
|
||||
}
|
||||
```
|
||||
|
||||
### Type `mdm_unenrolled`
|
||||
|
||||
Generated when a host is unenrolled from Fleet's MDM.
|
||||
|
||||
This activity contains the following fields:
|
||||
- "host_serial": Serial number of the host.
|
||||
- "installed_from_dep": Whether the host was enrolled via DEP.
|
||||
|
||||
#### Example
|
||||
|
||||
```json
|
||||
{
|
||||
"host_serial": "C08VQ2AXHT96",
|
||||
"installed_from_dep": true
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
<meta name="pageOrderInSection" value="1400">
|
||||
|
|
@ -16,10 +16,18 @@ func (ds *Datastore) NewActivity(ctx context.Context, user *fleet.User, activity
|
|||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "marshaling activity details")
|
||||
}
|
||||
|
||||
var userID *uint
|
||||
var userName *string
|
||||
if user != nil {
|
||||
userID = &user.ID
|
||||
userName = &user.Name
|
||||
}
|
||||
|
||||
_, err = ds.writer.ExecContext(ctx,
|
||||
`INSERT INTO activities (user_id, user_name, activity_type, details) VALUES(?,?,?,?)`,
|
||||
user.ID,
|
||||
user.Name,
|
||||
userID,
|
||||
userName,
|
||||
activity.ActivityName(),
|
||||
detailsBytes,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ func TestActivity(t *testing.T) {
|
|||
{"UsernameChange", testActivityUsernameChange},
|
||||
{"New", testActivityNew},
|
||||
{"ListActivitiesStreamed", testListActivitiesStreamed},
|
||||
{"EmptyUser", testActivityEmptyUser},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
|
|
@ -75,7 +76,7 @@ func testActivityUsernameChange(t *testing.T, ds *Datastore) {
|
|||
activities, err := ds.ListActivities(context.Background(), fleet.ListActivitiesOptions{})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, activities, 2)
|
||||
assert.Equal(t, "fullname", activities[0].ActorFullName)
|
||||
assert.Equal(t, "fullname", *activities[0].ActorFullName)
|
||||
|
||||
u.Name = "newname"
|
||||
err = ds.SaveUser(context.Background(), u)
|
||||
|
|
@ -84,7 +85,7 @@ func testActivityUsernameChange(t *testing.T, ds *Datastore) {
|
|||
activities, err = ds.ListActivities(context.Background(), fleet.ListActivitiesOptions{})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, activities, 2)
|
||||
assert.Equal(t, "newname", activities[0].ActorFullName)
|
||||
assert.Equal(t, "newname", *activities[0].ActorFullName)
|
||||
assert.Equal(t, "http://asd.com", *activities[0].ActorGravatar)
|
||||
assert.Equal(t, "email@asd.com", *activities[0].ActorEmail)
|
||||
|
||||
|
|
@ -94,7 +95,7 @@ func testActivityUsernameChange(t *testing.T, ds *Datastore) {
|
|||
activities, err = ds.ListActivities(context.Background(), fleet.ListActivitiesOptions{})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, activities, 2)
|
||||
assert.Equal(t, "fullname", activities[0].ActorFullName)
|
||||
assert.Equal(t, "fullname", *activities[0].ActorFullName)
|
||||
assert.Nil(t, activities[0].ActorGravatar)
|
||||
}
|
||||
|
||||
|
|
@ -125,7 +126,7 @@ func testActivityNew(t *testing.T, ds *Datastore) {
|
|||
activities, err := ds.ListActivities(context.Background(), opt)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, activities, 1)
|
||||
assert.Equal(t, "fullname", activities[0].ActorFullName)
|
||||
assert.Equal(t, "fullname", *activities[0].ActorFullName)
|
||||
assert.Equal(t, "test1", activities[0].Type)
|
||||
|
||||
opt = fleet.ListActivitiesOptions{
|
||||
|
|
@ -137,7 +138,7 @@ func testActivityNew(t *testing.T, ds *Datastore) {
|
|||
activities, err = ds.ListActivities(context.Background(), opt)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, activities, 1)
|
||||
assert.Equal(t, "fullname", activities[0].ActorFullName)
|
||||
assert.Equal(t, "fullname", *activities[0].ActorFullName)
|
||||
assert.Equal(t, "test2", activities[0].Type)
|
||||
|
||||
opt = fleet.ListActivitiesOptions{
|
||||
|
|
@ -208,3 +209,13 @@ func testListActivitiesStreamed(t *testing.T, ds *Datastore) {
|
|||
assert.Len(t, streamed, 1)
|
||||
require.Equal(t, streamed[0], activities[0])
|
||||
}
|
||||
|
||||
func testActivityEmptyUser(t *testing.T, ds *Datastore) {
|
||||
require.NoError(t, ds.NewActivity(context.Background(), nil, dummyActivity{
|
||||
name: "test1",
|
||||
details: map[string]interface{}{"detail": 1, "sometext": "aaa"},
|
||||
}))
|
||||
activities, err := ds.ListActivities(context.Background(), fleet.ListActivitiesOptions{})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, activities, 1)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -239,8 +239,12 @@ WHERE
|
|||
}
|
||||
|
||||
func (ds *Datastore) IngestMDMAppleDeviceFromCheckin(ctx context.Context, mdmHost fleet.MDMAppleHostDetails) error {
|
||||
appCfg, err := ds.AppConfig(ctx)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "ingest mdm apple host get app config")
|
||||
}
|
||||
return ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
|
||||
return ingestMDMAppleDeviceFromCheckinDB(ctx, tx, mdmHost, ds.logger)
|
||||
return ingestMDMAppleDeviceFromCheckinDB(ctx, tx, mdmHost, ds.logger, appCfg)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -249,6 +253,7 @@ func ingestMDMAppleDeviceFromCheckinDB(
|
|||
tx sqlx.ExtContext,
|
||||
mdmHost fleet.MDMAppleHostDetails,
|
||||
logger log.Logger,
|
||||
appCfg *fleet.AppConfig,
|
||||
) error {
|
||||
if mdmHost.SerialNumber == "" {
|
||||
return ctxerr.New(ctx, "ingest mdm apple host from checkin expected device serial number but got empty string")
|
||||
|
|
@ -263,7 +268,7 @@ func ingestMDMAppleDeviceFromCheckinDB(
|
|||
err := sqlx.GetContext(ctx, tx, &foundHost, stmt, mdmHost.UDID, mdmHost.SerialNumber)
|
||||
switch {
|
||||
case errors.Is(err, sql.ErrNoRows):
|
||||
return insertMDMAppleHostDB(ctx, tx, mdmHost, logger)
|
||||
return insertMDMAppleHostDB(ctx, tx, mdmHost, logger, appCfg)
|
||||
|
||||
case err != nil:
|
||||
return ctxerr.Wrap(ctx, err, "get mdm apple host by serial number or udid")
|
||||
|
|
@ -308,7 +313,13 @@ func updateMDMAppleHostDB(ctx context.Context, tx sqlx.ExtContext, hostID uint,
|
|||
return nil
|
||||
}
|
||||
|
||||
func insertMDMAppleHostDB(ctx context.Context, tx sqlx.ExtContext, mdmHost fleet.MDMAppleHostDetails, logger log.Logger) error {
|
||||
func insertMDMAppleHostDB(
|
||||
ctx context.Context,
|
||||
tx sqlx.ExtContext,
|
||||
mdmHost fleet.MDMAppleHostDetails,
|
||||
logger log.Logger,
|
||||
appCfg *fleet.AppConfig,
|
||||
) error {
|
||||
insertStmt := `
|
||||
INSERT INTO hosts (
|
||||
hardware_serial,
|
||||
|
|
@ -346,13 +357,16 @@ func insertMDMAppleHostDB(ctx context.Context, tx sqlx.ExtContext, mdmHost fleet
|
|||
}
|
||||
|
||||
if err := upsertMDMAppleHostDisplayNamesDB(ctx, tx, uint(id)); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "ingest mdm apple host upsert related tables")
|
||||
return ctxerr.Wrap(ctx, err, "ingest mdm apple host upsert display names")
|
||||
}
|
||||
|
||||
if err := upsertMDMAppleHostLabelMembershipDB(ctx, tx, logger, uint(id)); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "ingest mdm apple host upsert related tables")
|
||||
return ctxerr.Wrap(ctx, err, "ingest mdm apple host upsert label membership")
|
||||
}
|
||||
|
||||
if err := upsertMDMAppleHostMDMInfoDB(ctx, tx, appCfg.ServerSettings.ServerURL, false, uint(id)); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "ingest mdm apple host upsert MDM info")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -365,8 +379,13 @@ func (ds *Datastore) IngestMDMAppleDevicesFromDEPSync(ctx context.Context, devic
|
|||
return 0, nil
|
||||
}
|
||||
|
||||
appCfg, err := ds.AppConfig(ctx)
|
||||
if err != nil {
|
||||
return 0, ctxerr.Wrap(ctx, err, "ingest mdm apple host get app config")
|
||||
}
|
||||
|
||||
var resCount int64
|
||||
err := ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
|
||||
err = ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
|
||||
us, args := unionSelectDevices(filteredDevices)
|
||||
|
||||
stmt := fmt.Sprintf(`
|
||||
|
|
@ -416,11 +435,15 @@ func (ds *Datastore) IngestMDMAppleDevicesFromDEPSync(ctx context.Context, devic
|
|||
}
|
||||
|
||||
if err := upsertMDMAppleHostDisplayNamesDB(ctx, tx, hostIDs...); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "ingest mdm apple host upsert related tables")
|
||||
return ctxerr.Wrap(ctx, err, "ingest mdm apple host upsert display names")
|
||||
}
|
||||
|
||||
if err := upsertMDMAppleHostLabelMembershipDB(ctx, tx, ds.logger, hostIDs...); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "ingest mdm apple host upsert related tables")
|
||||
return ctxerr.Wrap(ctx, err, "ingest mdm apple host upsert label membership")
|
||||
}
|
||||
|
||||
if err := upsertMDMAppleHostMDMInfoDB(ctx, tx, appCfg.ServerSettings.ServerURL, true, hostIDs...); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "ingest mdm apple host upsert MDM info")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -448,6 +471,38 @@ func upsertMDMAppleHostDisplayNamesDB(ctx context.Context, tx sqlx.ExtContext, h
|
|||
return nil
|
||||
}
|
||||
|
||||
func upsertMDMAppleHostMDMInfoDB(ctx context.Context, tx sqlx.ExtContext, serverURL string, fromSync bool, hostIDs ...uint) error {
|
||||
result, err := tx.ExecContext(ctx, `
|
||||
INSERT INTO mobile_device_management_solutions (name, server_url) VALUES (?, ?)
|
||||
ON DUPLICATE KEY UPDATE server_url = VALUES(server_url)`,
|
||||
fleet.WellKnownMDMFleet, serverURL)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "last insert id mdm apple host")
|
||||
}
|
||||
|
||||
mdmID, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "last insert id mdm apple host")
|
||||
}
|
||||
|
||||
// if the device is coming from the DEP sync, we don't consider it enrolled
|
||||
// yet.
|
||||
enrolled := !fromSync
|
||||
|
||||
args := []interface{}{}
|
||||
parts := []string{}
|
||||
for _, id := range hostIDs {
|
||||
args = append(args, enrolled, serverURL, fromSync, mdmID, false, id)
|
||||
parts = append(parts, "(?, ?, ?, ?, ?, ?)")
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, fmt.Sprintf(`
|
||||
INSERT INTO host_mdm (enrolled, server_url, installed_from_dep, mdm_id, is_server, host_id) VALUES %s
|
||||
ON DUPLICATE KEY UPDATE enrolled = VALUES(enrolled)`, strings.Join(parts, ",")), args...)
|
||||
|
||||
return ctxerr.Wrap(ctx, err, "upsert host mdm info")
|
||||
}
|
||||
|
||||
func upsertMDMAppleHostLabelMembershipDB(ctx context.Context, tx sqlx.ExtContext, logger log.Logger, hostIDs ...uint) error {
|
||||
// Builtin label memberships are usually inserted when the first distributed
|
||||
// query results are received; however, we want to insert pending MDM hosts
|
||||
|
|
@ -484,6 +539,16 @@ func upsertMDMAppleHostLabelMembershipDB(ctx context.Context, tx sqlx.ExtContext
|
|||
return nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) UpdateHostTablesOnMDMUnenroll(ctx context.Context, uuid string) error {
|
||||
return ds.withTx(ctx, func(tx sqlx.ExtContext) error {
|
||||
_, err := tx.ExecContext(ctx, `
|
||||
UPDATE host_mdm
|
||||
SET enrolled = 0
|
||||
WHERE host_id = (SELECT id FROM hosts WHERE uuid = ?)`, uuid)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func filterMDMAppleDevices(devices []godep.Device) []godep.Device {
|
||||
var filtered []godep.Device
|
||||
for _, device := range devices {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import (
|
|||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/ptr"
|
||||
"github.com/fleetdm/fleet/v4/server/test"
|
||||
kitlog "github.com/go-kit/kit/log"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/micromdm/nanodep/godep"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
|
@ -32,7 +31,7 @@ func TestIngestMDMAppleDevicesFromDEPSync(t *testing.T) {
|
|||
SeenTime: time.Now().Add(-time.Duration(i) * time.Minute),
|
||||
OsqueryHostID: ptr.String(fmt.Sprintf("osquery-host-id_%d", i)),
|
||||
NodeKey: ptr.String(fmt.Sprintf("node-key_%d", i)),
|
||||
UUID: fmt.Sprintf(fmt.Sprintf("uuid_%d", i)),
|
||||
UUID: fmt.Sprintf("uuid_%d", i),
|
||||
HardwareSerial: fmt.Sprintf("serial_%d", i),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
|
@ -72,17 +71,18 @@ func TestIngestMDMAppleDevicesFromDEPSync(t *testing.T) {
|
|||
require.ElementsMatch(t, wantSerials, gotSerials)
|
||||
}
|
||||
|
||||
func TestIngestMDMAppleDeviceFromCheckin(t *testing.T) {
|
||||
func TestHandleMDMCheckinRequest(t *testing.T) {
|
||||
ds := CreateMySQLDS(t)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
fn func(t *testing.T, ds *Datastore)
|
||||
}{
|
||||
{"TestIngestMDMAppleCheckinHostAlreadyExistsInFleet", testIngestMDMAppleHostAlreadyExistsInFleet},
|
||||
{"TestIngestMDMApplCheckinAfterDEPSync", testIngestMDMAppleCheckinAfterDEPSync},
|
||||
{"TestIngestMDMApplCheckinBeforeDEPSync", testIngestMDMAppleCheckinBeforeDEPSync},
|
||||
{"TestIngestMDMAppleCheckinMultipleAuthenticateRequests", testIngestMDMAppleCheckinMultipleAuthenticateRequests},
|
||||
{"TestHostAlreadyExistsInFleet", testIngestMDMAppleHostAlreadyExistsInFleet},
|
||||
{"TestCheckinAfterDEPSync", testIngestMDMAppleCheckinAfterDEPSync},
|
||||
{"TestBeforeDEPSync", testIngestMDMAppleCheckinBeforeDEPSync},
|
||||
{"TestMultipleAuthenticateRequests", testIngestMDMAppleCheckinMultipleAuthenticateRequests},
|
||||
{"TestCheckOutRequests", testIngestMDMAppleCheckinCheckoutRequests},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
|
|
@ -96,12 +96,10 @@ func TestIngestMDMAppleDeviceFromCheckin(t *testing.T) {
|
|||
|
||||
func testIngestMDMAppleHostAlreadyExistsInFleet(t *testing.T, ds *Datastore) {
|
||||
ctx := context.Background()
|
||||
ingester := fleet.NewMDMAppleHostIngester(ds, kitlog.NewNopLogger())
|
||||
|
||||
testSerial := "test-serial"
|
||||
testUUID := "test-uuid"
|
||||
|
||||
_, err := ds.NewHost(ctx, &fleet.Host{
|
||||
host, err := ds.NewHost(ctx, &fleet.Host{
|
||||
Hostname: "test-host-name",
|
||||
DetailUpdatedAt: time.Now(),
|
||||
LabelUpdatedAt: time.Now(),
|
||||
|
|
@ -113,27 +111,38 @@ func testIngestMDMAppleHostAlreadyExistsInFleet(t *testing.T, ds *Datastore) {
|
|||
HardwareSerial: testSerial,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = ds.SetOrUpdateMDMData(ctx, host.ID, false, false, "https://fleetdm.com", true, "Fleet MDM")
|
||||
require.NoError(t, err)
|
||||
hosts := listHostsCheckCount(t, ds, fleet.TeamFilter{User: test.UserAdmin}, fleet.HostListOptions{}, 1)
|
||||
require.Equal(t, testSerial, hosts[0].HardwareSerial)
|
||||
require.Equal(t, testUUID, hosts[0].UUID)
|
||||
|
||||
err = ingester.Ingest(ctx, &http.Request{
|
||||
err = fleet.HandleMDMCheckinRequest(ctx, &http.Request{
|
||||
Header: map[string][]string{
|
||||
"Content-Type": {"application/x-apple-aspen-mdm-checkin"},
|
||||
},
|
||||
Method: http.MethodPost,
|
||||
Body: io.NopCloser(strings.NewReader(xmlForTest("Authenticate", testSerial, testUUID, "MacBook Pro"))),
|
||||
})
|
||||
}, ds)
|
||||
require.NoError(t, err)
|
||||
hosts = listHostsCheckCount(t, ds, fleet.TeamFilter{User: test.UserAdmin}, fleet.HostListOptions{}, 1)
|
||||
require.Equal(t, testSerial, hosts[0].HardwareSerial)
|
||||
require.Equal(t, testUUID, hosts[0].UUID)
|
||||
|
||||
// an activity is created
|
||||
activities, err := ds.ListActivities(context.Background(), fleet.ListActivitiesOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, activities, 1)
|
||||
require.Empty(t, activities[0].ActorID)
|
||||
require.JSONEq(
|
||||
t,
|
||||
`{"host_serial":"test-serial", "installed_from_dep":true}`,
|
||||
string(*activities[0].Details),
|
||||
)
|
||||
}
|
||||
|
||||
func testIngestMDMAppleCheckinAfterDEPSync(t *testing.T, ds *Datastore) {
|
||||
ctx := context.Background()
|
||||
ingester := fleet.NewMDMAppleHostIngester(ds, kitlog.NewNopLogger())
|
||||
|
||||
testSerial := "test-serial"
|
||||
testUUID := "test-uuid"
|
||||
|
||||
|
|
@ -152,37 +161,57 @@ func testIngestMDMAppleCheckinAfterDEPSync(t *testing.T, ds *Datastore) {
|
|||
checkMDMHostRelatedTables(t, ds, hosts[0].ID)
|
||||
|
||||
// now simulate the initial MDM checkin by that same host
|
||||
err = ingester.Ingest(ctx, &http.Request{
|
||||
err = fleet.HandleMDMCheckinRequest(ctx, &http.Request{
|
||||
Header: map[string][]string{
|
||||
"Content-Type": {"application/x-apple-aspen-mdm-checkin"},
|
||||
},
|
||||
Method: http.MethodPost,
|
||||
Body: io.NopCloser(strings.NewReader(xmlForTest("Authenticate", testSerial, testUUID, "MacBook Pro"))),
|
||||
})
|
||||
}, ds)
|
||||
require.NoError(t, err)
|
||||
hosts = listHostsCheckCount(t, ds, fleet.TeamFilter{User: test.UserAdmin}, fleet.HostListOptions{}, 1)
|
||||
require.Equal(t, testSerial, hosts[0].HardwareSerial)
|
||||
require.Equal(t, testUUID, hosts[0].UUID)
|
||||
checkMDMHostRelatedTables(t, ds, hosts[0].ID)
|
||||
|
||||
// an activity is created
|
||||
activities, err := ds.ListActivities(context.Background(), fleet.ListActivitiesOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, activities, 1)
|
||||
require.Empty(t, activities[0].ActorID)
|
||||
require.JSONEq(
|
||||
t,
|
||||
`{"host_serial":"test-serial", "installed_from_dep":true}`,
|
||||
string(*activities[0].Details),
|
||||
)
|
||||
}
|
||||
|
||||
func testIngestMDMAppleCheckinBeforeDEPSync(t *testing.T, ds *Datastore) {
|
||||
ctx := context.Background()
|
||||
ingester := fleet.NewMDMAppleHostIngester(ds, kitlog.NewNopLogger())
|
||||
|
||||
testSerial := "test-serial"
|
||||
testUUID := "test-uuid"
|
||||
|
||||
// ingest host on initial mdm checkin
|
||||
err := ingester.Ingest(ctx, &http.Request{
|
||||
err := fleet.HandleMDMCheckinRequest(ctx, &http.Request{
|
||||
Header: map[string][]string{
|
||||
"Content-Type": {"application/x-apple-aspen-mdm-checkin"},
|
||||
},
|
||||
Method: http.MethodPost,
|
||||
Body: io.NopCloser(strings.NewReader(xmlForTest("Authenticate", testSerial, testUUID, "MacBook Pro"))),
|
||||
})
|
||||
}, ds)
|
||||
require.NoError(t, err)
|
||||
|
||||
// an activity is created
|
||||
activities, err := ds.ListActivities(context.Background(), fleet.ListActivitiesOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, activities, 1)
|
||||
require.Empty(t, activities[0].ActorID)
|
||||
require.JSONEq(
|
||||
t,
|
||||
`{"host_serial":"test-serial", "installed_from_dep":false}`,
|
||||
string(*activities[0].Details),
|
||||
)
|
||||
|
||||
hosts := listHostsCheckCount(t, ds, fleet.TeamFilter{User: test.UserAdmin}, fleet.HostListOptions{}, 1)
|
||||
require.Equal(t, testSerial, hosts[0].HardwareSerial)
|
||||
require.Equal(t, testUUID, hosts[0].UUID)
|
||||
|
|
@ -203,37 +232,70 @@ func testIngestMDMAppleCheckinBeforeDEPSync(t *testing.T, ds *Datastore) {
|
|||
|
||||
func testIngestMDMAppleCheckinMultipleAuthenticateRequests(t *testing.T, ds *Datastore) {
|
||||
ctx := context.Background()
|
||||
ingester := fleet.NewMDMAppleHostIngester(ds, kitlog.NewNopLogger())
|
||||
|
||||
testSerial := "test-serial"
|
||||
testUUID := "test-uuid"
|
||||
|
||||
err := ingester.Ingest(ctx, &http.Request{
|
||||
err := fleet.HandleMDMCheckinRequest(ctx, &http.Request{
|
||||
Header: map[string][]string{
|
||||
"Content-Type": {"application/x-apple-aspen-mdm-checkin"},
|
||||
},
|
||||
Method: http.MethodPost,
|
||||
Body: io.NopCloser(strings.NewReader(xmlForTest("Authenticate", testSerial, testUUID, "MacBook Pro"))),
|
||||
})
|
||||
}, ds)
|
||||
require.NoError(t, err)
|
||||
hosts := listHostsCheckCount(t, ds, fleet.TeamFilter{User: test.UserAdmin}, fleet.HostListOptions{}, 1)
|
||||
require.Equal(t, testSerial, hosts[0].HardwareSerial)
|
||||
require.Equal(t, testUUID, hosts[0].UUID)
|
||||
|
||||
// duplicate Authenticate request has no effect
|
||||
err = ingester.Ingest(ctx, &http.Request{
|
||||
err = fleet.HandleMDMCheckinRequest(ctx, &http.Request{
|
||||
Header: map[string][]string{
|
||||
"Content-Type": {"application/x-apple-aspen-mdm-checkin"},
|
||||
},
|
||||
Method: http.MethodPost,
|
||||
Body: io.NopCloser(strings.NewReader(xmlForTest("Authenticate", testSerial, testUUID, "MacBook Pro"))),
|
||||
})
|
||||
}, ds)
|
||||
require.NoError(t, err)
|
||||
hosts = listHostsCheckCount(t, ds, fleet.TeamFilter{User: test.UserAdmin}, fleet.HostListOptions{}, 1)
|
||||
require.Equal(t, testSerial, hosts[0].HardwareSerial)
|
||||
require.Equal(t, testUUID, hosts[0].UUID)
|
||||
}
|
||||
|
||||
func testIngestMDMAppleCheckinCheckoutRequests(t *testing.T, ds *Datastore) {
|
||||
ctx := context.Background()
|
||||
testSerial := "test-serial"
|
||||
testUUID := "test-uuid"
|
||||
err := fleet.HandleMDMCheckinRequest(ctx, &http.Request{
|
||||
Header: map[string][]string{
|
||||
"Content-Type": {"application/x-apple-aspen-mdm-checkin"},
|
||||
},
|
||||
Method: http.MethodPost,
|
||||
Body: io.NopCloser(strings.NewReader(xmlForTest("Authenticate", testSerial, testUUID, "MacBook Pro"))),
|
||||
}, ds)
|
||||
require.NoError(t, err)
|
||||
|
||||
// CheckOut request updates MDM data and adds an activity
|
||||
err = fleet.HandleMDMCheckinRequest(ctx, &http.Request{
|
||||
Header: map[string][]string{
|
||||
"Content-Type": {"application/x-apple-aspen-mdm-checkin"},
|
||||
},
|
||||
Method: http.MethodPost,
|
||||
Body: io.NopCloser(strings.NewReader(xmlForTest("CheckOut", "", testUUID, ""))),
|
||||
}, ds)
|
||||
require.NoError(t, err)
|
||||
|
||||
activities, err := ds.ListActivities(context.Background(), fleet.ListActivitiesOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, activities, 2)
|
||||
require.Equal(t, "mdm_unenrolled", activities[1].Type)
|
||||
require.Empty(t, activities[1].ActorID)
|
||||
require.JSONEq(
|
||||
t,
|
||||
`{"host_serial":"test-serial", "installed_from_dep":false}`,
|
||||
string(*activities[1].Details),
|
||||
)
|
||||
}
|
||||
|
||||
// checkMDMHostRelatedTables checks that rows are inserted for new MDM hosts in each of
|
||||
// host_display_names, host_seen_times, and label_membership. Note that related tables records for
|
||||
// pre-existing hosts are created outside of the MDM enrollment flows so they are not checked in
|
||||
|
|
@ -250,6 +312,21 @@ func checkMDMHostRelatedTables(t *testing.T, ds *Datastore, hostID uint) {
|
|||
require.Len(t, labelsOK, 2)
|
||||
require.True(t, labelsOK[0])
|
||||
require.True(t, labelsOK[1])
|
||||
|
||||
appCfg, err := ds.AppConfig(context.Background())
|
||||
require.NoError(t, err)
|
||||
var hmdm fleet.HostMDM
|
||||
err = sqlx.GetContext(context.Background(), ds.reader, &hmdm, `SELECT host_id, server_url, mdm_id FROM host_mdm WHERE host_id = ?`, hostID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, hostID, hmdm.HostID)
|
||||
require.Equal(t, appCfg.ServerSettings.ServerURL, hmdm.ServerURL)
|
||||
require.NotEmpty(t, hmdm.MDMID)
|
||||
|
||||
var mdmSolution fleet.MDMSolution
|
||||
err = sqlx.GetContext(context.Background(), ds.reader, &mdmSolution, `SELECT name, server_url FROM mobile_device_management_solutions WHERE id = ?`, hmdm.MDMID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fleet.WellKnownMDMFleet, mdmSolution.Name)
|
||||
require.Equal(t, appCfg.ServerSettings.ServerURL, mdmSolution.ServerURL)
|
||||
}
|
||||
|
||||
func xmlForTest(msgType string, serial string, udid string, model string) string {
|
||||
|
|
|
|||
|
|
@ -2234,6 +2234,26 @@ func (ds *Datastore) GetHostMDM(ctx context.Context, hostID uint) (*fleet.HostMD
|
|||
return &hmdm, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) GetHostMDMCheckinInfo(ctx context.Context, hostUUID string) (*fleet.HostMDMCheckinInfo, error) {
|
||||
var hmdm fleet.HostMDMCheckinInfo
|
||||
err := sqlx.GetContext(ctx, ds.reader, &hmdm, `
|
||||
SELECT
|
||||
h.hardware_serial, hm.installed_from_dep
|
||||
FROM
|
||||
hosts h
|
||||
JOIN
|
||||
host_mdm hm
|
||||
ON h.id = hm.host_id
|
||||
WHERE h.uuid = ?`, hostUUID)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, ctxerr.Wrap(ctx, notFound("MDM").WithMessage(hostUUID))
|
||||
}
|
||||
return nil, ctxerr.Wrapf(ctx, err, "getting data from host_mdm for host_uuid %s", hostUUID)
|
||||
}
|
||||
return &hmdm, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) GetMDMSolution(ctx context.Context, mdmID uint) (*fleet.MDMSolution, error) {
|
||||
var solution fleet.MDMSolution
|
||||
err := sqlx.GetContext(ctx, ds.reader, &solution, `
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@ func TestHosts(t *testing.T) {
|
|||
{"HostIDsByOSID", testHostIDsByOSID},
|
||||
{"SetOrUpdateHostDisksEncryption", testHostsSetOrUpdateHostDisksEncryption},
|
||||
{"TestHostOrder", testHostOrder},
|
||||
{"GetHostMDMCheckinInfo", testHostsGetHostMDMCheckinInfo},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
|
|
@ -5561,3 +5562,28 @@ func testHostsSetOrUpdateHostDisksEncryption(t *testing.T, ds *Datastore) {
|
|||
require.NoError(t, err)
|
||||
require.True(t, *h.DiskEncryptionEnabled)
|
||||
}
|
||||
|
||||
func testHostsGetHostMDMCheckinInfo(t *testing.T, ds *Datastore) {
|
||||
ctx := context.Background()
|
||||
host, err := ds.NewHost(context.Background(), &fleet.Host{
|
||||
DetailUpdatedAt: time.Now(),
|
||||
LabelUpdatedAt: time.Now(),
|
||||
PolicyUpdatedAt: time.Now(),
|
||||
SeenTime: time.Now(),
|
||||
NodeKey: ptr.String("1"),
|
||||
UUID: "1",
|
||||
OsqueryHostID: ptr.String("1"),
|
||||
Hostname: "foo.local",
|
||||
PrimaryIP: "192.168.1.1",
|
||||
PrimaryMac: "30-65-EC-6F-C4-58",
|
||||
HardwareSerial: "123456789",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = ds.SetOrUpdateMDMData(ctx, host.ID, false, true, "https://fleetdm.com", true, fleet.WellKnownMDMFleet)
|
||||
require.NoError(t, err)
|
||||
|
||||
info, err := ds.GetHostMDMCheckinInfo(ctx, host.UUID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, host.HardwareSerial, info.HardwareSerial)
|
||||
require.Equal(t, true, info.InstalledFromDEP)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ var ActivityDetailsList = []ActivityDetails{
|
|||
ActivityTypeDeletedUserGlobalRole{},
|
||||
ActivityTypeChangedUserTeamRole{},
|
||||
ActivityTypeDeletedUserTeamRole{},
|
||||
|
||||
ActivityTypeMDMEnrolled{},
|
||||
ActivityTypeMDMUnenrolled{},
|
||||
}
|
||||
|
||||
type ActivityDetails interface {
|
||||
|
|
@ -429,7 +432,7 @@ func (a ActivityTypeUserAddedBySSO) Documentation() (activity string, details st
|
|||
type Activity struct {
|
||||
CreateTimestamp
|
||||
ID uint `json:"id" db:"id"`
|
||||
ActorFullName string `json:"actor_full_name" db:"name"`
|
||||
ActorFullName *string `json:"actor_full_name" db:"name"`
|
||||
ActorID *uint `json:"actor_id" db:"user_id"`
|
||||
ActorGravatar *string `json:"actor_gravatar" db:"gravatar_url"`
|
||||
ActorEmail *string `json:"actor_email" db:"email"`
|
||||
|
|
@ -610,6 +613,44 @@ func (a ActivityTypeDeletedUserTeamRole) Documentation() (activity string, detai
|
|||
}`
|
||||
}
|
||||
|
||||
type ActivityTypeMDMEnrolled struct {
|
||||
HostSerial string `json:"host_serial"`
|
||||
InstalledFromDEP bool `json:"installed_from_dep"`
|
||||
}
|
||||
|
||||
func (a ActivityTypeMDMEnrolled) ActivityName() string {
|
||||
return "mdm_enrolled"
|
||||
}
|
||||
|
||||
func (a ActivityTypeMDMEnrolled) Documentation() (activity string, details string, detailsExample string) {
|
||||
return `Generated when a host is enrolled in Fleet's MDM.`,
|
||||
`This activity contains the following fields:
|
||||
- "host_serial": Serial number of the host.
|
||||
- "installed_from_dep": Whether the host was enrolled via DEP.`, `{
|
||||
"host_serial": "C08VQ2AXHT96",
|
||||
"installed_from_dep": true
|
||||
}`
|
||||
}
|
||||
|
||||
type ActivityTypeMDMUnenrolled struct {
|
||||
HostSerial string `json:"host_serial"`
|
||||
InstalledFromDEP bool `json:"installed_from_dep"`
|
||||
}
|
||||
|
||||
func (a ActivityTypeMDMUnenrolled) ActivityName() string {
|
||||
return "mdm_unenrolled"
|
||||
}
|
||||
|
||||
func (a ActivityTypeMDMUnenrolled) Documentation() (activity string, details string, detailsExample string) {
|
||||
return `Generated when a host is unenrolled from Fleet's MDM.`,
|
||||
`This activity contains the following fields:
|
||||
- "host_serial": Serial number of the host.
|
||||
- "installed_from_dep": Whether the host was enrolled via DEP.`, `{
|
||||
"host_serial": "C08VQ2AXHT96",
|
||||
"installed_from_dep": true
|
||||
}`
|
||||
}
|
||||
|
||||
// AuthzType implement AuthzTyper to be able to verify access to activities
|
||||
func (*Activity) AuthzType() string {
|
||||
return "activity"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
kitlog "github.com/go-kit/kit/log"
|
||||
"github.com/micromdm/nanodep/godep"
|
||||
nanohttp "github.com/micromdm/nanomdm/http"
|
||||
"github.com/micromdm/nanomdm/mdm"
|
||||
|
|
@ -188,43 +187,59 @@ type MDMAppleHostDetails struct {
|
|||
Model string
|
||||
}
|
||||
|
||||
// MDMHostIngester represents the interface used to ingest an MDM device as a Fleet host pending enrollment.
|
||||
type MDMHostIngester interface {
|
||||
Ingest(context.Context, *http.Request) error
|
||||
}
|
||||
|
||||
// MDMAppleHostIngester implements the MDMHostIngester interface in connection with Apple MDM services.
|
||||
type MDMAppleHostIngester struct {
|
||||
ds Datastore
|
||||
logger kitlog.Logger
|
||||
}
|
||||
|
||||
// NewMDMAppleHostIngester returns a new instance of an MDMAppleHostIngester.
|
||||
func NewMDMAppleHostIngester(ds Datastore, logger kitlog.Logger) *MDMAppleHostIngester {
|
||||
return &MDMAppleHostIngester{ds: ds, logger: logger}
|
||||
}
|
||||
|
||||
// Ingest handles incoming http requests that follow Apple's MDM checkin protocol. For valid
|
||||
// checkin requests, Ingest decodes the XML body and ingests new host details into the associated
|
||||
// datastore. See also https://developer.apple.com/documentation/devicemanagement/check-in.
|
||||
func (ingester *MDMAppleHostIngester) Ingest(ctx context.Context, r *http.Request) error {
|
||||
// HandleMDMCheckinRequest handles incoming http requests
|
||||
// that follow Apple's MDM checkin protocol.
|
||||
//
|
||||
// - For valid Authenticate checkin requests, it decodes
|
||||
// the XML body and ingests new host details into the
|
||||
// associated datastore.
|
||||
// - For TokenUpdate and CheckOut requests it creates
|
||||
// activity logs.
|
||||
//
|
||||
// See also
|
||||
// https://developer.apple.com/documentation/devicemanagement/check-in.
|
||||
func HandleMDMCheckinRequest(ctx context.Context, r *http.Request, ds Datastore) error {
|
||||
if isMDMAppleCheckinReq(r) {
|
||||
host := MDMAppleHostDetails{}
|
||||
|
||||
ok, err := decodeMDMAppleCheckinReq(r, &host)
|
||||
switch {
|
||||
case err != nil:
|
||||
msg, err := decodeMDMAppleCheckinReq(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode checkin request: %w", err)
|
||||
case !ok:
|
||||
return nil
|
||||
default:
|
||||
// continue
|
||||
}
|
||||
|
||||
if err := ingester.ds.IngestMDMAppleDeviceFromCheckin(ctx, host); err != nil {
|
||||
return err
|
||||
switch m := msg.(type) {
|
||||
case *mdm.Authenticate:
|
||||
host := MDMAppleHostDetails{}
|
||||
host.SerialNumber = m.SerialNumber
|
||||
host.UDID = m.UDID
|
||||
host.Model = m.Model
|
||||
if err := ds.IngestMDMAppleDeviceFromCheckin(ctx, host); err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := ds.GetHostMDMCheckinInfo(ctx, m.Enrollment.UDID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ds.NewActivity(ctx, nil, &ActivityTypeMDMEnrolled{
|
||||
HostSerial: info.HardwareSerial,
|
||||
InstalledFromDEP: info.InstalledFromDEP,
|
||||
})
|
||||
case *mdm.CheckOut:
|
||||
if err := ds.UpdateHostTablesOnMDMUnenroll(ctx, m.UDID); err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := ds.GetHostMDMCheckinInfo(ctx, m.Enrollment.UDID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ds.NewActivity(ctx, nil, &ActivityTypeMDMUnenrolled{
|
||||
HostSerial: info.HardwareSerial,
|
||||
InstalledFromDEP: info.InstalledFromDEP,
|
||||
})
|
||||
default:
|
||||
// these aren't the requests you're looking for, move along
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -233,23 +248,15 @@ func isMDMAppleCheckinReq(r *http.Request) bool {
|
|||
return strings.HasPrefix(contentType, "application/x-apple-aspen-mdm-checkin")
|
||||
}
|
||||
|
||||
func decodeMDMAppleCheckinReq(r *http.Request, dest *MDMAppleHostDetails) (bool, error) {
|
||||
func decodeMDMAppleCheckinReq(r *http.Request) (interface{}, error) {
|
||||
bodyBytes, err := nanohttp.ReadAllAndReplaceBody(r)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return nil, err
|
||||
}
|
||||
msg, err := mdm.DecodeCheckin(bodyBytes)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
switch m := msg.(type) {
|
||||
case *mdm.Authenticate:
|
||||
dest.SerialNumber = m.SerialNumber
|
||||
dest.UDID = m.UDID
|
||||
dest.Model = m.Model
|
||||
return true, nil
|
||||
default:
|
||||
// these aren't the requests you're looking for, move along
|
||||
return false, nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/micromdm/nanomdm/mdm"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
|
@ -34,7 +35,6 @@ func TestDecodeMDMAppleCheckinRequest(t *testing.T) {
|
|||
testSerial := "test-serial"
|
||||
testUDID := "test-udid"
|
||||
|
||||
// decode host details from request XML if MessageType is "Authenticate"
|
||||
req := &http.Request{
|
||||
Header: map[string][]string{
|
||||
"Content-Type": {"application/x-apple-aspen-mdm-checkin"},
|
||||
|
|
@ -42,28 +42,14 @@ func TestDecodeMDMAppleCheckinRequest(t *testing.T) {
|
|||
Method: http.MethodPost,
|
||||
Body: io.NopCloser(strings.NewReader(xmlForTest("Authenticate", testSerial, testUDID, "MacBook Pro"))),
|
||||
}
|
||||
host := &MDMAppleHostDetails{}
|
||||
ok, err := decodeMDMAppleCheckinReq(req, host)
|
||||
msg, err := decodeMDMAppleCheckinReq(req)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, msg)
|
||||
msgAuth, ok := msg.(*mdm.Authenticate)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, testSerial, host.SerialNumber)
|
||||
require.Equal(t, testUDID, host.UDID)
|
||||
require.Equal(t, "MacBook Pro", host.Model)
|
||||
|
||||
// do nothing if MessageType is not "Authenticate"
|
||||
req = &http.Request{
|
||||
Header: map[string][]string{
|
||||
"Content-Type": {"application/x-apple-aspen-mdm-checkin"},
|
||||
},
|
||||
Method: http.MethodPost,
|
||||
Body: io.NopCloser(strings.NewReader(xmlForTest("TokenUpdate", testSerial, testUDID, "MacBook Pro"))),
|
||||
}
|
||||
host = &MDMAppleHostDetails{}
|
||||
ok, err = decodeMDMAppleCheckinReq(req, host)
|
||||
require.NoError(t, err)
|
||||
require.True(t, !ok)
|
||||
require.Empty(t, host.SerialNumber)
|
||||
require.Empty(t, host.UDID)
|
||||
require.Equal(t, testSerial, msgAuth.SerialNumber)
|
||||
require.Equal(t, testUDID, msgAuth.UDID)
|
||||
require.Equal(t, "MacBook Pro", msgAuth.Model)
|
||||
}
|
||||
|
||||
func xmlForTest(msgType string, serial string, udid string, model string) string {
|
||||
|
|
|
|||
|
|
@ -254,6 +254,7 @@ type Datastore interface {
|
|||
GetHostMunkiVersion(ctx context.Context, hostID uint) (string, error)
|
||||
GetHostMunkiIssues(ctx context.Context, hostID uint) ([]*HostMunkiIssue, error)
|
||||
GetHostMDM(ctx context.Context, hostID uint) (*HostMDM, error)
|
||||
GetHostMDMCheckinInfo(ctx context.Context, hostUUID string) (*HostMDMCheckinInfo, error)
|
||||
|
||||
AggregatedMunkiVersion(ctx context.Context, teamID *uint) ([]AggregatedMunkiVersion, time.Time, error)
|
||||
AggregatedMunkiIssues(ctx context.Context, teamID *uint) ([]AggregatedMunkiIssue, time.Time, error)
|
||||
|
|
@ -437,6 +438,8 @@ type Datastore interface {
|
|||
// upgraded from a prior version).
|
||||
CleanupHostOperatingSystems(ctx context.Context) error
|
||||
|
||||
UpdateHostTablesOnMDMUnenroll(ctx context.Context, uuid string) error
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// ActivitiesStore
|
||||
|
||||
|
|
|
|||
|
|
@ -398,6 +398,7 @@ const (
|
|||
WellKnownMDMVMWare = "VMware Workspace ONE"
|
||||
WellKnownMDMIntune = "Intune"
|
||||
WellKnownMDMSimpleMDM = "SimpleMDM"
|
||||
WellKnownMDMFleet = "Fleet"
|
||||
)
|
||||
|
||||
var mdmNameFromServerURLChecks = map[string]string{
|
||||
|
|
@ -564,3 +565,8 @@ type EnrollHostLimiter interface {
|
|||
CanEnrollNewHost(ctx context.Context) (ok bool, err error)
|
||||
SyncEnrolledHostIDs(ctx context.Context) error
|
||||
}
|
||||
|
||||
type HostMDMCheckinInfo struct {
|
||||
HardwareSerial string `json:"hardware_serial" db:"hardware_serial"`
|
||||
InstalledFromDEP bool `json:"installed_from_dep" db:"installed_from_dep"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -200,6 +200,8 @@ type GetHostMunkiIssuesFunc func(ctx context.Context, hostID uint) ([]*fleet.Hos
|
|||
|
||||
type GetHostMDMFunc func(ctx context.Context, hostID uint) (*fleet.HostMDM, error)
|
||||
|
||||
type GetHostMDMCheckinInfoFunc func(ctx context.Context, hostUUID string) (*fleet.HostMDMCheckinInfo, error)
|
||||
|
||||
type AggregatedMunkiVersionFunc func(ctx context.Context, teamID *uint) ([]fleet.AggregatedMunkiVersion, time.Time, error)
|
||||
|
||||
type AggregatedMunkiIssuesFunc func(ctx context.Context, teamID *uint) ([]fleet.AggregatedMunkiIssue, time.Time, error)
|
||||
|
|
@ -336,6 +338,8 @@ type UpdateHostOperatingSystemFunc func(ctx context.Context, hostID uint, hostOS
|
|||
|
||||
type CleanupHostOperatingSystemsFunc func(ctx context.Context) error
|
||||
|
||||
type UpdateHostTablesOnMDMUnenrollFunc func(ctx context.Context, uuid string) error
|
||||
|
||||
type NewActivityFunc func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error
|
||||
|
||||
type ListActivitiesFunc func(ctx context.Context, opt fleet.ListActivitiesOptions) ([]*fleet.Activity, error)
|
||||
|
|
@ -800,6 +804,9 @@ type DataStore struct {
|
|||
GetHostMDMFunc GetHostMDMFunc
|
||||
GetHostMDMFuncInvoked bool
|
||||
|
||||
GetHostMDMCheckinInfoFunc GetHostMDMCheckinInfoFunc
|
||||
GetHostMDMCheckinInfoFuncInvoked bool
|
||||
|
||||
AggregatedMunkiVersionFunc AggregatedMunkiVersionFunc
|
||||
AggregatedMunkiVersionFuncInvoked bool
|
||||
|
||||
|
|
@ -1004,6 +1011,9 @@ type DataStore struct {
|
|||
CleanupHostOperatingSystemsFunc CleanupHostOperatingSystemsFunc
|
||||
CleanupHostOperatingSystemsFuncInvoked bool
|
||||
|
||||
UpdateHostTablesOnMDMUnenrollFunc UpdateHostTablesOnMDMUnenrollFunc
|
||||
UpdateHostTablesOnMDMUnenrollFuncInvoked bool
|
||||
|
||||
NewActivityFunc NewActivityFunc
|
||||
NewActivityFuncInvoked bool
|
||||
|
||||
|
|
@ -1746,6 +1756,11 @@ func (s *DataStore) GetHostMDM(ctx context.Context, hostID uint) (*fleet.HostMDM
|
|||
return s.GetHostMDMFunc(ctx, hostID)
|
||||
}
|
||||
|
||||
func (s *DataStore) GetHostMDMCheckinInfo(ctx context.Context, hostUUID string) (*fleet.HostMDMCheckinInfo, error) {
|
||||
s.GetHostMDMCheckinInfoFuncInvoked = true
|
||||
return s.GetHostMDMCheckinInfoFunc(ctx, hostUUID)
|
||||
}
|
||||
|
||||
func (s *DataStore) AggregatedMunkiVersion(ctx context.Context, teamID *uint) ([]fleet.AggregatedMunkiVersion, time.Time, error) {
|
||||
s.AggregatedMunkiVersionFuncInvoked = true
|
||||
return s.AggregatedMunkiVersionFunc(ctx, teamID)
|
||||
|
|
@ -2086,6 +2101,11 @@ func (s *DataStore) CleanupHostOperatingSystems(ctx context.Context) error {
|
|||
return s.CleanupHostOperatingSystemsFunc(ctx)
|
||||
}
|
||||
|
||||
func (s *DataStore) UpdateHostTablesOnMDMUnenroll(ctx context.Context, uuid string) error {
|
||||
s.UpdateHostTablesOnMDMUnenrollFuncInvoked = true
|
||||
return s.UpdateHostTablesOnMDMUnenrollFunc(ctx, uuid)
|
||||
}
|
||||
|
||||
func (s *DataStore) NewActivity(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
|
||||
s.NewActivityFuncInvoked = true
|
||||
return s.NewActivityFunc(ctx, user, activity)
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ func (s *integrationTestSuite) TestQueryCreationLogsActivity() {
|
|||
for _, activity := range activities.Activities {
|
||||
if activity.Type == "created_saved_query" {
|
||||
found = true
|
||||
assert.Equal(t, "Test Name admin1@example.com", activity.ActorFullName)
|
||||
assert.Equal(t, "Test Name admin1@example.com", *activity.ActorFullName)
|
||||
require.NotNil(t, activity.ActorGravatar)
|
||||
assert.Equal(t, "http://iii.com", *activity.ActorGravatar)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue