diff --git a/changes/14601-add-disk-encryption-detail-host-mdm b/changes/14601-add-disk-encryption-detail-host-mdm new file mode 100644 index 0000000000..e213139b62 --- /dev/null +++ b/changes/14601-add-disk-encryption-detail-host-mdm @@ -0,0 +1,2 @@ +- Updated `GET hosts/:id` API response to include additional detail for disk encryption in case of + device client errors. diff --git a/server/datastore/mysql/microsoft_mdm.go b/server/datastore/mysql/microsoft_mdm.go index 7dd3a696fc..f82d4164a7 100644 --- a/server/datastore/mysql/microsoft_mdm.go +++ b/server/datastore/mysql/microsoft_mdm.go @@ -254,31 +254,32 @@ WHERE return &res, nil } -func (ds *Datastore) GetMDMWindowsBitLockerStatus(ctx context.Context, host *fleet.Host) (*fleet.DiskEncryptionStatus, error) { +func (ds *Datastore) GetMDMWindowsBitLockerStatus(ctx context.Context, host *fleet.Host) (*fleet.HostMDMDiskEncryption, error) { + hde := &fleet.HostMDMDiskEncryption{} if host == nil { - return nil, errors.New("host cannot be nil") + return hde, errors.New("host cannot be nil") } if host.Platform != "windows" { // Generally, the caller should have already checked this, but just in case we log and // return nil level.Debug(ds.logger).Log("msg", "cannot get bitlocker status for non-windows host", "host_id", host.ID) - return nil, nil + return hde, nil } if host.MDMInfo != nil && host.MDMInfo.IsServer { // It is currently expected that server hosts do not have a bitlocker status so we can skip // the query and return nil. We log for potential debugging in case this changes in the future. level.Debug(ds.logger).Log("msg", "no bitlocker status for server host", "host_id", host.ID) - return nil, nil + return hde, nil } enabled, err := ds.getConfigEnableDiskEncryption(ctx, host.TeamID) if err != nil { - return nil, err + return hde, err } if !enabled { - return nil, nil + return hde, nil } // Note action_required and removing_enforcement are not applicable to Windows hosts @@ -289,7 +290,8 @@ SELECT WHEN (%s) THEN '%s' WHEN (%s) THEN '%s' WHEN (%s) THEN '%s' - END AS status + END AS status, + COALESCE(client_error, '') as detail FROM host_mdm hmdm LEFT JOIN host_disk_encryption_keys hdek ON hmdm.host_id = hdek.host_id @@ -306,17 +308,22 @@ WHERE fleet.DiskEncryptionFailed, ) - var des fleet.DiskEncryptionStatus - if err := sqlx.GetContext(ctx, ds.reader(ctx), &des, stmt, host.ID); err != nil { - if err == sql.ErrNoRows { - // At this point we know disk encryption is enabled so if we don't have a record for the - // host then we treat it as enforcing and log for potential debugging - level.Debug(ds.logger).Log("msg", "no bitlocker status found for host", "host_id", host.ID) - des = fleet.DiskEncryptionEnforcing - return &des, nil + var dest struct { + Status fleet.DiskEncryptionStatus `db:"status"` + Detail string `db:"detail"` + } + if err := sqlx.GetContext(ctx, ds.reader(ctx), &dest, stmt, host.ID); err != nil { + if err != sql.ErrNoRows { + return &fleet.HostMDMDiskEncryption{}, err } - return nil, err + // At this point we know disk encryption is enabled so if there are no rows for the + // host then we treat it as enforcing and log for potential debugging + level.Debug(ds.logger).Log("msg", "no bitlocker status found for host", "host_id", host.ID) + dest.Status = fleet.DiskEncryptionEnforcing } - return &des, nil + return &fleet.HostMDMDiskEncryption{ + Status: &dest.Status, + Detail: dest.Detail, + }, nil } diff --git a/server/datastore/mysql/microsoft_mdm_test.go b/server/datastore/mysql/microsoft_mdm_test.go index e1fe97b2c3..45f87e8c96 100644 --- a/server/datastore/mysql/microsoft_mdm_test.go +++ b/server/datastore/mysql/microsoft_mdm_test.go @@ -133,7 +133,8 @@ func TestMDMWindowsDiskEncryption(t *testing.T) { bls, err := ds.GetMDMWindowsBitLockerStatus(ctx, h) require.NoError(t, err) require.NotNil(t, bls) - require.Equal(t, expected, *bls) + require.NotNil(t, bls.Status) + require.Equal(t, expected, *bls.Status) } } diff --git a/server/fleet/datastore.go b/server/fleet/datastore.go index dd4d0257d1..4574751c80 100644 --- a/server/fleet/datastore.go +++ b/server/fleet/datastore.go @@ -1053,7 +1053,7 @@ type Datastore interface { // // Note that the returned status will be nil if the host is reported to be a Windows // server or if disk encryption is disabled for the host's team (or no team, as applicable). - GetMDMWindowsBitLockerStatus(ctx context.Context, host *Host) (*DiskEncryptionStatus, error) + GetMDMWindowsBitLockerStatus(ctx context.Context, host *Host) (*HostMDMDiskEncryption, error) /////////////////////////////////////////////////////////////////////////////// // Host Script Results diff --git a/server/fleet/hosts.go b/server/fleet/hosts.go index 5fece04a6c..f0e2e70a3f 100644 --- a/server/fleet/hosts.go +++ b/server/fleet/hosts.go @@ -376,6 +376,7 @@ type HostMDMOSSettings struct { type HostMDMDiskEncryption struct { Status *DiskEncryptionStatus `json:"status" db:"-" csv:"-"` + Detail string `json:"detail" db:"-" csv:"-"` } type DiskEncryptionStatus string @@ -431,11 +432,14 @@ type HostMDMMacOSSetup struct { BootstrapPackageName string `db:"bootstrap_package_name" json:"bootstrap_package_name" csv:"-"` } -// DetermineMacOSDiskEncryptionStatus determines the disk encryption status for the +// DetermineHostMDMDiskEncryptionMacOS determines the disk encryption status for the // host based on the file-vault profile in its list of profiles and whether its -// disk encryption key is available and decryptable. The file-vault profile -// identifier is received as argument to avoid a circular dependency. -func (d *MDMHostData) DetermineMacOSDiskEncryptionStatus(profiles []HostMDMAppleProfile, fileVaultIdentifier string) { +// disk encryption key is available and decryptable. It sets the status value in MacOSSettings on +// the HostMDMData struct. It also returns a pointer to a newly populated HostMDMDiskEncryption +// struct intended for use in the host details response. +// +// The file-vault profile identifier is received as argument to avoid a circular dependency. +func (d *MDMHostData) DetermineHostMDMDiskEncryptionMacOS(profiles []HostMDMAppleProfile, fileVaultIdentifier string) *HostMDMDiskEncryption { var settings MDMHostMacOSSettings var fvprof *HostMDMAppleProfile @@ -503,6 +507,17 @@ func (d *MDMHostData) DetermineMacOSDiskEncryptionStatus(profiles []HostMDMApple } } d.MacOSSettings = &settings + + var status *DiskEncryptionStatus + if settings.DiskEncryption != nil { + status = settings.DiskEncryption + } + var detail string + if fvprof != nil { + detail = fvprof.Detail + } + + return &HostMDMDiskEncryption{Status: status, Detail: detail} } func (d *MDMHostData) ProfileStatusFromDiskEncryptionState(currStatus *MDMAppleDeliveryStatus) *MDMAppleDeliveryStatus { diff --git a/server/mock/datastore_mock.go b/server/mock/datastore_mock.go index f419c17d14..6141737673 100644 --- a/server/mock/datastore_mock.go +++ b/server/mock/datastore_mock.go @@ -690,7 +690,7 @@ type MDMWindowsDeleteEnrolledDeviceWithDeviceIDFunc func(ctx context.Context, md type GetMDMWindowsBitLockerSummaryFunc func(ctx context.Context, teamID *uint) (*fleet.MDMWindowsBitLockerSummary, error) -type GetMDMWindowsBitLockerStatusFunc func(ctx context.Context, host *fleet.Host) (*fleet.DiskEncryptionStatus, error) +type GetMDMWindowsBitLockerStatusFunc func(ctx context.Context, host *fleet.Host) (*fleet.HostMDMDiskEncryption, error) type NewHostScriptExecutionRequestFunc func(ctx context.Context, request *fleet.HostScriptRequestPayload) (*fleet.HostScriptResult, error) @@ -4114,7 +4114,7 @@ func (s *DataStore) GetMDMWindowsBitLockerSummary(ctx context.Context, teamID *u return s.GetMDMWindowsBitLockerSummaryFunc(ctx, teamID) } -func (s *DataStore) GetMDMWindowsBitLockerStatus(ctx context.Context, host *fleet.Host) (*fleet.DiskEncryptionStatus, error) { +func (s *DataStore) GetMDMWindowsBitLockerStatus(ctx context.Context, host *fleet.Host) (*fleet.HostMDMDiskEncryption, error) { s.mu.Lock() s.GetMDMWindowsBitLockerStatusFuncInvoked = true s.mu.Unlock() diff --git a/server/service/hosts.go b/server/service/hosts.go index c7905d8dc2..2779ce9aac 100644 --- a/server/service/hosts.go +++ b/server/service/hosts.go @@ -924,11 +924,11 @@ func (svc *Service) getHostDetails(ctx context.Context, host *fleet.Host, opts f switch host.Platform { case "windows": if ac.MDM.WindowsEnabledAndConfigured && license.IsPremium(ctx) { - bls, err := svc.ds.GetMDMWindowsBitLockerStatus(ctx, host) + hde, err := svc.ds.GetMDMWindowsBitLockerStatus(ctx, host) if err != nil { return nil, ctxerr.Wrap(ctx, err, "get host mdm bitlocker status") } - host.MDM.OSSettings.DiskEncryption.Status = bls + host.MDM.OSSettings.DiskEncryption = *hde } case "darwin": if ac.MDM.EnabledAndConfigured { @@ -939,8 +939,8 @@ func (svc *Service) getHostDetails(ctx context.Context, host *fleet.Host, opts f // determine disk encryption and action required here based on profiles and // raw decryptable key status. - host.MDM.DetermineMacOSDiskEncryptionStatus(profs, mobileconfig.FleetFileVaultPayloadIdentifier) - host.MDM.OSSettings.DiskEncryption.Status = host.MDM.MacOSSettings.DiskEncryption + hde := host.MDM.DetermineHostMDMDiskEncryptionMacOS(profs, mobileconfig.FleetFileVaultPayloadIdentifier) + host.MDM.OSSettings.DiskEncryption = *hde for _, p := range profs { if p.Identifier == mobileconfig.FleetFileVaultPayloadIdentifier { diff --git a/server/service/hosts_test.go b/server/service/hosts_test.go index 896079935e..178082468a 100644 --- a/server/service/hosts_test.go +++ b/server/service/hosts_test.go @@ -228,6 +228,7 @@ func TestHostDetailsMDMAppleDiskEncryption(t *testing.T) { Identifier: mobileconfig.FleetFileVaultPayloadIdentifier, Status: &fleet.MDMAppleDeliveryFailed, OperationType: fleet.MDMAppleOperationTypeInstall, + Detail: "some mdm profile install error", }, fleet.DiskEncryptionFailed, "", @@ -280,6 +281,7 @@ func TestHostDetailsMDMAppleDiskEncryption(t *testing.T) { Identifier: mobileconfig.FleetFileVaultPayloadIdentifier, Status: &fleet.MDMAppleDeliveryFailed, OperationType: fleet.MDMAppleOperationTypeRemove, + Detail: "some mdm profile removal error", }, fleet.DiskEncryptionFailed, "", @@ -327,11 +329,13 @@ func TestHostDetailsMDMAppleDiskEncryption(t *testing.T) { if c.wantState == "" { require.Nil(t, hostDetail.MDM.MacOSSettings.DiskEncryption) require.Nil(t, hostDetail.MDM.OSSettings.DiskEncryption.Status) + require.Empty(t, hostDetail.MDM.OSSettings.DiskEncryption.Detail) } else { require.NotNil(t, hostDetail.MDM.MacOSSettings.DiskEncryption) require.Equal(t, c.wantState, *hostDetail.MDM.MacOSSettings.DiskEncryption) require.NotNil(t, hostDetail.MDM.OSSettings.DiskEncryption.Status) require.Equal(t, c.wantState, *hostDetail.MDM.OSSettings.DiskEncryption.Status) + require.Equal(t, c.fvProf.Detail, hostDetail.MDM.OSSettings.DiskEncryption.Detail) } if c.wantAction == "" { require.Nil(t, hostDetail.MDM.MacOSSettings.ActionRequired) @@ -343,6 +347,7 @@ func TestHostDetailsMDMAppleDiskEncryption(t *testing.T) { require.NotNil(t, hostDetail.MDM.Profiles) profs := *hostDetail.MDM.Profiles require.Equal(t, c.wantStatus, profs[0].Status) + require.Equal(t, c.fvProf.Detail, profs[0].Detail) } else { require.Nil(t, *hostDetail.MDM.Profiles) } @@ -396,11 +401,11 @@ func TestHostDetailsOSSettings(t *testing.T) { ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) { return &fleet.AppConfig{MDM: fleet.MDM{EnabledAndConfigured: true, WindowsEnabledAndConfigured: true}}, nil } - ds.GetMDMWindowsBitLockerStatusFunc = func(ctx context.Context, host *fleet.Host) (*fleet.DiskEncryptionStatus, error) { + ds.GetMDMWindowsBitLockerStatusFunc = func(ctx context.Context, host *fleet.Host) (*fleet.HostMDMDiskEncryption, error) { if c.wantStatus == "" { return nil, nil } - return &c.wantStatus, nil + return &fleet.HostMDMDiskEncryption{Status: &c.wantStatus, Detail: ""}, nil } ds.GetHostMDMProfilesFunc = func(ctx context.Context, uuid string) ([]fleet.HostMDMAppleProfile, error) { return nil, nil @@ -469,9 +474,9 @@ func TestHostDetailsOSSettingsWindowsOnly(t *testing.T) { ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) { return &fleet.AppConfig{MDM: fleet.MDM{WindowsEnabledAndConfigured: true}}, nil } - ds.GetMDMWindowsBitLockerStatusFunc = func(ctx context.Context, host *fleet.Host) (*fleet.DiskEncryptionStatus, error) { + ds.GetMDMWindowsBitLockerStatusFunc = func(ctx context.Context, host *fleet.Host) (*fleet.HostMDMDiskEncryption, error) { verified := fleet.DiskEncryptionVerified - return &verified, nil + return &fleet.HostMDMDiskEncryption{Status: &verified, Detail: ""}, nil } ds.GetHostMDMProfilesFunc = func(ctx context.Context, uuid string) ([]fleet.HostMDMAppleProfile, error) { return nil, nil