Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include Linux disk encryption status in configuration profiles aggregate status response when applicable #24114

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
18 changes: 6 additions & 12 deletions ee/server/service/teams.go
Original file line number Diff line number Diff line change
Expand Up @@ -1516,15 +1516,15 @@ func unmarshalWithGlobalDefaults(b *json.RawMessage) (fleet.Features, error) {
}

func (svc *Service) updateTeamMDMDiskEncryption(ctx context.Context, tm *fleet.Team, enable *bool) error {
var didUpdate, didUpdateMacOSDiskEncryption bool
var didUpdate bool
if enable != nil {
if svc.config.Server.PrivateKey == "" {
return ctxerr.New(ctx, "Missing required private key. Learn how to configure the private key here: https://fleetdm.com/learn-more-about/fleet-server-private-key")
}
if tm.Config.MDM.EnableDiskEncryption != *enable {
if *enable && svc.config.Server.PrivateKey == "" {
return ctxerr.New(ctx, "Missing required private key. Learn how to configure the private key here: https://fleetdm.com/learn-more-about/fleet-server-private-key")
}

tm.Config.MDM.EnableDiskEncryption = *enable
didUpdate = true
didUpdateMacOSDiskEncryption = true
}
}

Expand All @@ -1537,13 +1537,7 @@ func (svc *Service) updateTeamMDMDiskEncryption(ctx context.Context, tm *fleet.T
if err != nil {
return err
}

// macOS-specific stuff. For legacy reasons we check if apple is configured
// via `appCfg.MDM.EnabledAndConfigured`
//
// TODO: is there a missing bitlocker activity feed item? (see same TODO on
// other methods that deal with disk encryption)
if appCfg.MDM.EnabledAndConfigured && didUpdateMacOSDiskEncryption {
if appCfg.MDM.EnabledAndConfigured {
var act fleet.ActivityDetails
if tm.Config.MDM.EnableDiskEncryption {
act = fleet.ActivityTypeEnabledMacosDiskEncryption{TeamID: &tm.ID, TeamName: &tm.Name}
Expand Down
2 changes: 1 addition & 1 deletion frontend/services/entities/mdm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ const mdmService = {
},

getProfilesStatusSummary: (teamId: number) => {
let { MDM_PROFILES_STATUS_SUMMARY: path } = endpoints;
let { PROFILES_STATUS_SUMMARY: path } = endpoints;

if (teamId) {
path = `${path}?${buildQueryStringFromParams({ team_id: teamId })}`;
Expand Down
2 changes: 1 addition & 1 deletion frontend/utilities/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export default {
MDM_PROFILE: (id: string) => `/${API_VERSION}/fleet/mdm/profiles/${id}`,

MDM_UPDATE_APPLE_SETTINGS: `/${API_VERSION}/fleet/mdm/apple/settings`,
MDM_PROFILES_STATUS_SUMMARY: `/${API_VERSION}/fleet/mdm/profiles/summary`,
PROFILES_STATUS_SUMMARY: `/${API_VERSION}/fleet/configuration_profiles/summary`,
MDM_DISK_ENCRYPTION_SUMMARY: `/${API_VERSION}/fleet/mdm/disk_encryption/summary`,
MDM_APPLE_SSO: `/${API_VERSION}/fleet/mdm/sso`,
MDM_APPLE_ENROLLMENT_PROFILE: (token: string, ref?: string) => {
Expand Down
2 changes: 1 addition & 1 deletion server/datastore/mysql/app_configs.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ func (ds *Datastore) AggregateEnrollSecretPerTeam(ctx context.Context) ([]*fleet
return secrets, nil
}

func (ds *Datastore) getConfigEnableDiskEncryption(ctx context.Context, teamID *uint) (bool, error) {
func (ds *Datastore) GetConfigEnableDiskEncryption(ctx context.Context, teamID *uint) (bool, error) {
if teamID != nil && *teamID > 0 {
tc, err := ds.TeamMDMConfig(ctx, *teamID)
if err != nil {
Expand Down
6 changes: 3 additions & 3 deletions server/datastore/mysql/app_configs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ func testGetConfigEnableDiskEncryption(t *testing.T, ds *Datastore) {
require.NoError(t, err)
require.False(t, ac.MDM.EnableDiskEncryption.Value)

enabled, err := ds.getConfigEnableDiskEncryption(ctx, nil)
enabled, err := ds.GetConfigEnableDiskEncryption(ctx, nil)
require.NoError(t, err)
require.False(t, enabled)

Expand All @@ -461,7 +461,7 @@ func testGetConfigEnableDiskEncryption(t *testing.T, ds *Datastore) {
require.NoError(t, err)
require.True(t, ac.MDM.EnableDiskEncryption.Value)

enabled, err = ds.getConfigEnableDiskEncryption(ctx, nil)
enabled, err = ds.GetConfigEnableDiskEncryption(ctx, nil)
require.NoError(t, err)
require.True(t, enabled)

Expand All @@ -474,7 +474,7 @@ func testGetConfigEnableDiskEncryption(t *testing.T, ds *Datastore) {
require.NotNil(t, tm)
require.False(t, tm.Config.MDM.EnableDiskEncryption)

enabled, err = ds.getConfigEnableDiskEncryption(ctx, &team1.ID)
enabled, err = ds.GetConfigEnableDiskEncryption(ctx, &team1.ID)
require.NoError(t, err)
require.False(t, enabled)

Expand Down
2 changes: 1 addition & 1 deletion server/datastore/mysql/hosts.go
Original file line number Diff line number Diff line change
Expand Up @@ -1218,7 +1218,7 @@ func (ds *Datastore) applyHostFilters(
return "", nil, ctxerr.Wrap(ctx, err, "building query to filter macOS settings status")
}
sqlStmt, whereParams = filterHostsByMacOSDiskEncryptionStatus(sqlStmt, opt, whereParams)
if enableDiskEncryption, err := ds.getConfigEnableDiskEncryption(ctx, opt.TeamFilter); err != nil {
if enableDiskEncryption, err := ds.GetConfigEnableDiskEncryption(ctx, opt.TeamFilter); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return "", nil, ctxerr.Wrap(
ctx, &fleet.BadRequestError{
Expand Down
2 changes: 1 addition & 1 deletion server/datastore/mysql/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -662,7 +662,7 @@ func (ds *Datastore) applyHostLabelFilters(ctx context.Context, filter fleet.Tea
}
query, whereParams = filterHostsByMacOSDiskEncryptionStatus(query, opt, whereParams)
query, whereParams = filterHostsByMDMBootstrapPackageStatus(query, opt, whereParams)
if enableDiskEncryption, err := ds.getConfigEnableDiskEncryption(ctx, opt.TeamFilter); err != nil {
if enableDiskEncryption, err := ds.GetConfigEnableDiskEncryption(ctx, opt.TeamFilter); err != nil {
return "", nil, err
} else if opt.OSSettingsFilter.IsValid() {
query, whereParams, err = ds.filterHostsByOSSettingsStatus(query, opt, whereParams, enableDiskEncryption)
Expand Down
6 changes: 3 additions & 3 deletions server/datastore/mysql/microsoft_mdm.go
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@ AND (
}

func (ds *Datastore) GetMDMWindowsBitLockerSummary(ctx context.Context, teamID *uint) (*fleet.MDMWindowsBitLockerSummary, error) {
enabled, err := ds.getConfigEnableDiskEncryption(ctx, teamID)
enabled, err := ds.GetConfigEnableDiskEncryption(ctx, teamID)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -655,7 +655,7 @@ func (ds *Datastore) GetMDMWindowsBitLockerStatus(ctx context.Context, host *fle
return nil, nil
}

enabled, err := ds.getConfigEnableDiskEncryption(ctx, host.TeamID)
enabled, err := ds.GetConfigEnableDiskEncryption(ctx, host.TeamID)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -887,7 +887,7 @@ func subqueryHostsMDMWindowsOSSettingsStatusVerified() (string, []interface{}, e
}

func (ds *Datastore) GetMDMWindowsProfilesSummary(ctx context.Context, teamID *uint) (*fleet.MDMProfilesSummary, error) {
includeBitLocker, err := ds.getConfigEnableDiskEncryption(ctx, teamID)
includeBitLocker, err := ds.GetConfigEnableDiskEncryption(ctx, teamID)
if err != nil {
return nil, err
}
Expand Down
1 change: 1 addition & 0 deletions server/fleet/datastore.go
Original file line number Diff line number Diff line change
Expand Up @@ -899,6 +899,7 @@ type Datastore interface {
GetHostEmails(ctx context.Context, hostUUID string, source string) ([]string, error)
SetOrUpdateHostDisksSpace(ctx context.Context, hostID uint, gigsAvailable, percentAvailable, gigsTotal float64) error

GetConfigEnableDiskEncryption(ctx context.Context, teamID *uint) (bool, error)
SetOrUpdateHostDisksEncryption(ctx context.Context, hostID uint, encrypted bool) error
// SetOrUpdateHostDiskEncryptionKey sets the base64, encrypted key for
// a host
Expand Down
4 changes: 2 additions & 2 deletions server/fleet/mdm.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,8 +310,8 @@ type MDMDiskEncryptionSummary struct {
RemovingEnforcement MDMPlatformsCounts `db:"removing_enforcement" json:"removing_enforcement"`
}

// MDMProfilesSummary reports the number of hosts being managed with MDM configuration
// profiles. Each host may be counted in only one of four mutually-exclusive categories:
// MDMProfilesSummary reports the number of hosts being managed with configuration
// profiles and/or disk encryption. Each host may be counted in only one of four mutually-exclusive categories:
// Failed, Pending, Verifying, or Verified.
type MDMProfilesSummary struct {
// Verified includes each host where Fleet has verified the installation of all of the
Expand Down
5 changes: 5 additions & 0 deletions server/fleet/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -1059,6 +1059,11 @@ type Service interface {
// Returns empty status if the host is not a supported Linux host
LinuxHostDiskEncryptionStatus(ctx context.Context, host Host) (HostMDMDiskEncryption, error)

// GetMDMLinuxProfilesSummary summarizes the current status of Linux disk encryption for
// the provided team (or hosts without a team if teamId is nil), or returns zeroes if disk
// encryption is not enforced on the selected team
GetMDMLinuxProfilesSummary(ctx context.Context, teamId *uint) (MDMProfilesSummary, error)

///////////////////////////////////////////////////////////////////////////////
// Common MDM

Expand Down
12 changes: 12 additions & 0 deletions server/mock/datastore_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,8 @@ type GetHostEmailsFunc func(ctx context.Context, hostUUID string, source string)

type SetOrUpdateHostDisksSpaceFunc func(ctx context.Context, hostID uint, gigsAvailable float64, percentAvailable float64, gigsTotal float64) error

type GetConfigEnableDiskEncryptionFunc func(ctx context.Context, teamID *uint) (bool, error)

type SetOrUpdateHostDisksEncryptionFunc func(ctx context.Context, hostID uint, encrypted bool) error

type SetOrUpdateHostDiskEncryptionKeyFunc func(ctx context.Context, hostID uint, encryptedBase64Key string, clientError string, decryptable *bool) error
Expand Down Expand Up @@ -2087,6 +2089,9 @@ type DataStore struct {
SetOrUpdateHostDisksSpaceFunc SetOrUpdateHostDisksSpaceFunc
SetOrUpdateHostDisksSpaceFuncInvoked bool

GetConfigEnableDiskEncryptionFunc GetConfigEnableDiskEncryptionFunc
GetConfigEnableDiskEncryptionFuncInvoked bool

SetOrUpdateHostDisksEncryptionFunc SetOrUpdateHostDisksEncryptionFunc
SetOrUpdateHostDisksEncryptionFuncInvoked bool

Expand Down Expand Up @@ -5034,6 +5039,13 @@ func (s *DataStore) SetOrUpdateHostDisksSpace(ctx context.Context, hostID uint,
return s.SetOrUpdateHostDisksSpaceFunc(ctx, hostID, gigsAvailable, percentAvailable, gigsTotal)
}

func (s *DataStore) GetConfigEnableDiskEncryption(ctx context.Context, teamID *uint) (bool, error) {
s.mu.Lock()
s.GetConfigEnableDiskEncryptionFuncInvoked = true
s.mu.Unlock()
return s.GetConfigEnableDiskEncryptionFunc(ctx, teamID)
}

func (s *DataStore) SetOrUpdateHostDisksEncryption(ctx context.Context, hostID uint, encrypted bool) error {
s.mu.Lock()
s.SetOrUpdateHostDisksEncryptionFuncInvoked = true
Expand Down
2 changes: 1 addition & 1 deletion server/service/appconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte, applyOpts fle
// 1. To get the JSON value from the database
// 2. To update fields with the incoming values
if newAppConfig.MDM.EnableDiskEncryption.Valid {
if svc.config.Server.PrivateKey == "" {
if newAppConfig.MDM.EnableDiskEncryption.Value && svc.config.Server.PrivateKey == "" {
return nil, ctxerr.New(ctx, "Missing required private key. Learn how to configure the private key here: https://fleetdm.com/learn-more-about/fleet-server-private-key")
}
appConfig.MDM.EnableDiskEncryption = newAppConfig.MDM.EnableDiskEncryption
Expand Down
9 changes: 6 additions & 3 deletions server/service/apple_mdm.go
Original file line number Diff line number Diff line change
Expand Up @@ -2152,20 +2152,23 @@ func (svc *Service) updateAppConfigMDMDiskEncryption(ctx context.Context, enable
return err
}

var didUpdate, didUpdateMacOSDiskEncryption bool
var didUpdate bool
if enabled != nil {
if ac.MDM.EnableDiskEncryption.Value != *enabled {
if *enabled && svc.config.Server.PrivateKey == "" {
return ctxerr.New(ctx, "Missing required private key. Learn how to configure the private key here: https://fleetdm.com/learn-more-about/fleet-server-private-key")
}

ac.MDM.EnableDiskEncryption = optjson.SetBool(*enabled)
didUpdate = true
didUpdateMacOSDiskEncryption = true
}
}

if didUpdate {
if err := svc.ds.SaveAppConfig(ctx, ac); err != nil {
return err
}
if didUpdateMacOSDiskEncryption {
if ac.MDM.EnabledAndConfigured { // if macOS MDM is configured, set up FileVault escrow
var act fleet.ActivityDetails
if ac.MDM.EnableDiskEncryption.Value {
act = fleet.ActivityTypeEnabledMacosDiskEncryption{}
Expand Down
7 changes: 6 additions & 1 deletion server/service/integration_core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8638,6 +8638,11 @@ func (s *integrationTestSuite) TestGetHostDiskEncryption() {
require.Equal(t, hostLin.ID, getHostResp.Host.ID)
require.True(t, *getHostResp.Host.DiskEncryptionEnabled)

// should succeed as we no longer require MDM to access this endpoint, as Linux encryption doesn't require MDM
var profiles getMDMProfilesSummaryResponse
s.DoJSON("GET", "/api/latest/fleet/configuration_profiles/summary", getMDMProfilesSummaryRequest{}, http.StatusOK, &profiles)
s.DoJSON("GET", "/api/latest/fleet/mdm/profiles/summary", getMDMProfilesSummaryRequest{}, http.StatusOK, &profiles)

// set unencrypted for all hosts
require.NoError(t, s.ds.SetOrUpdateHostDisksEncryption(context.Background(), hostWin.ID, false))
require.NoError(t, s.ds.SetOrUpdateHostDisksEncryption(context.Background(), hostMac.ID, false))
Expand All @@ -8653,7 +8658,7 @@ func (s *integrationTestSuite) TestGetHostDiskEncryption() {
require.Equal(t, hostMac.ID, getHostResp.Host.ID)
require.False(t, *getHostResp.Host.DiskEncryptionEnabled)

// Linux does not return false, it omits the field when false
// Linux may omit the field when false
getHostResp = getHostResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d", hostLin.ID), nil, http.StatusOK, &getHostResp)
require.Equal(t, hostLin.ID, getHostResp.Host.ID)
Expand Down
90 changes: 90 additions & 0 deletions server/service/integration_enterprise_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2881,6 +2881,96 @@ func (s *integrationEnterpriseTestSuite) TestAppleOSUpdatesTeamConfig() {
}, http.StatusUnprocessableEntity, &tmResp)
}

func (s *integrationEnterpriseTestSuite) TestLinuxDiskEncryption() {
t := s.T()

// create a Linux host
noTeamHost, err := s.ds.NewHost(context.Background(), &fleet.Host{
DetailUpdatedAt: time.Now(),
LabelUpdatedAt: time.Now(),
PolicyUpdatedAt: time.Now(),
SeenTime: time.Now(),
NodeKey: ptr.String(strings.ReplaceAll(t.Name(), "/", "_") + "3"),
OsqueryHostID: ptr.String(strings.ReplaceAll(t.Name(), "/", "_") + "3"),
UUID: t.Name() + "3",
Hostname: t.Name() + "foo3.local",
PrimaryIP: "192.168.1.3",
PrimaryMac: "30-65-EC-6F-C4-60",
Platform: "ubuntu",
OSVersion: "Ubuntu 22.04",
})
require.NoError(t, err)
team, err := s.ds.NewTeam(context.Background(), &fleet.Team{Name: "A team"})
require.NoError(t, err)
teamID := ptr.Uint(team.ID)
teamHost, err := s.ds.NewHost(context.Background(), &fleet.Host{
DetailUpdatedAt: time.Now(),
LabelUpdatedAt: time.Now(),
PolicyUpdatedAt: time.Now(),
SeenTime: time.Now(),
NodeKey: ptr.String(strings.ReplaceAll(t.Name(), "/", "_") + "2"),
OsqueryHostID: ptr.String(strings.ReplaceAll(t.Name(), "/", "_") + "2"),
UUID: t.Name() + "2",
Hostname: t.Name() + "foo2.local",
PrimaryIP: "192.168.1.2",
PrimaryMac: "30-65-EC-6F-C4-59",
Platform: "rhel",
OSVersion: "Fedora 38.0",
TeamID: teamID,
})
require.NoError(t, err)

// NO TEAM //

// config profiles endpoint should work but show all zeroes
var profileSummary getMDMProfilesSummaryResponse
s.DoJSON("GET", "/api/latest/fleet/configuration_profiles/summary", getMDMProfilesSummaryRequest{}, http.StatusOK, &profileSummary)
require.Equal(t, fleet.MDMProfilesSummary{}, profileSummary.MDMProfilesSummary)

// set encrypted for host
require.NoError(t, s.ds.SetOrUpdateHostDisksEncryption(context.Background(), noTeamHost.ID, true))

// should still show zeroes
s.DoJSON("GET", "/api/latest/fleet/configuration_profiles/summary", getMDMProfilesSummaryRequest{}, http.StatusOK, &profileSummary)
require.Equal(t, fleet.MDMProfilesSummary{}, profileSummary.MDMProfilesSummary)

// turn on disk encryption enforcement
s.Do("POST", "/api/latest/fleet/disk_encryption", updateDiskEncryptionRequest{EnableDiskEncryption: true}, http.StatusNoContent)

// should show the Linux host as pending
s.DoJSON("GET", "/api/latest/fleet/configuration_profiles/summary", getMDMProfilesSummaryRequest{}, http.StatusOK, &profileSummary)
require.Equal(t, fleet.MDMProfilesSummary{Pending: 1}, profileSummary.MDMProfilesSummary)

// encryption summary should succeed (Linux encryption doesn't require MDM)
var summary getMDMDiskEncryptionSummaryResponse
s.DoJSON("GET", "/api/latest/fleet/mdm/disk_encryption/summary", getMDMDiskEncryptionSummaryRequest{}, http.StatusOK, &summary)
s.DoJSON("GET", "/api/latest/fleet/disk_encryption", getMDMDiskEncryptionSummaryRequest{}, http.StatusOK, &summary)
// disk is encrypted but key hasn't been escrowed yet
require.Equal(t, fleet.MDMDiskEncryptionSummary{ActionRequired: fleet.MDMPlatformsCounts{Linux: 1}}, *summary.MDMDiskEncryptionSummary)

// TEAM //
s.DoJSON("GET", "/api/latest/fleet/configuration_profiles/summary", getMDMProfilesSummaryRequest{TeamID: teamID}, http.StatusOK, &profileSummary)
require.Equal(t, fleet.MDMProfilesSummary{}, profileSummary.MDMProfilesSummary)

// set encrypted for host
require.NoError(t, s.ds.SetOrUpdateHostDisksEncryption(context.Background(), teamHost.ID, true))

// should still show zeroes
s.DoJSON("GET", "/api/latest/fleet/configuration_profiles/summary", getMDMProfilesSummaryRequest{TeamID: teamID}, http.StatusOK, &profileSummary)
require.Equal(t, fleet.MDMProfilesSummary{}, profileSummary.MDMProfilesSummary)

// turn on disk encryption enforcement for team
s.Do("POST", "/api/latest/fleet/disk_encryption", updateDiskEncryptionRequest{TeamID: teamID, EnableDiskEncryption: true}, http.StatusNoContent)

// should show the Linux host as pending
s.DoJSON("GET", "/api/latest/fleet/configuration_profiles/summary", getMDMProfilesSummaryRequest{TeamID: teamID}, http.StatusOK, &profileSummary)
require.Equal(t, fleet.MDMProfilesSummary{Pending: 1}, profileSummary.MDMProfilesSummary)

// encryption summary should show host as action required
s.DoJSON("GET", "/api/latest/fleet/disk_encryption", getMDMDiskEncryptionSummaryRequest{TeamID: teamID}, http.StatusOK, &summary)
require.Equal(t, fleet.MDMDiskEncryptionSummary{ActionRequired: fleet.MDMPlatformsCounts{Linux: 1}}, *summary.MDMDiskEncryptionSummary)
}

func (s *integrationEnterpriseTestSuite) TestListDevicePolicies() {
t := s.T()
ctx := context.Background()
Expand Down
26 changes: 26 additions & 0 deletions server/service/linux_mdm.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package service
import (
"context"

"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/fleet"
)

Expand Down Expand Up @@ -42,3 +43,28 @@ func (svc *Service) LinuxHostDiskEncryptionStatus(ctx context.Context, host flee
Status: &verified,
}, nil
}

func (svc *Service) GetMDMLinuxProfilesSummary(ctx context.Context, teamId *uint) (summary fleet.MDMProfilesSummary, err error) {
if err = svc.authz.Authorize(ctx, fleet.MDMConfigProfileAuthz{TeamID: teamId}, fleet.ActionRead); err != nil {
return summary, ctxerr.Wrap(ctx, err)
}

// Linux doesn't have configuration profiles, so if we aren't enforcing disk encryption we have nothing to report
includeDiskEncryptionStats, err := svc.ds.GetConfigEnableDiskEncryption(ctx, teamId)
if err != nil {
return summary, ctxerr.Wrap(ctx, err)
} else if !includeDiskEncryptionStats {
return summary, nil
}

counts, err := svc.ds.GetLinuxDiskEncryptionSummary(ctx, teamId)
if err != nil {
return summary, ctxerr.Wrap(ctx, err)
}

return fleet.MDMProfilesSummary{
Verified: counts.Verified,
Pending: counts.ActionRequired,
Failed: counts.Failed,
}, nil
}
Loading
Loading