diff --git a/changes/35532-include-host-cert-templates-on-os-status b/changes/35532-include-host-cert-templates-on-os-status new file mode 100644 index 0000000000..1c9eca5aa4 --- /dev/null +++ b/changes/35532-include-host-cert-templates-on-os-status @@ -0,0 +1 @@ +* updated queries / APIs that drive the OS Settings UI to include the status of host cert templates. \ No newline at end of file diff --git a/server/datastore/mysql/certificate_templates.go b/server/datastore/mysql/certificate_templates.go index 3564c2191a..736ce365b8 100644 --- a/server/datastore/mysql/certificate_templates.go +++ b/server/datastore/mysql/certificate_templates.go @@ -2,6 +2,7 @@ package mysql import ( "context" + "errors" "fmt" "strings" @@ -205,3 +206,82 @@ func (ds *Datastore) UpdateCertificateStatus(ctx context.Context, hostUUID strin return nil } + +func (ds *Datastore) GetHostCertificateTemplates(ctx context.Context, hostUUID string) ([]fleet.HostCertificateTemplate, error) { + if hostUUID == "" { + return nil, errors.New("hostUUID cannot be empty") + } + + stmt := ` +SELECT + ct.name, + hct.status +FROM host_certificate_templates hct + INNER JOIN certificate_templates ct ON ct.id = hct.certificate_template_id +WHERE hct.host_uuid = ?` + + var hTemplates []fleet.HostCertificateTemplate + if err := sqlx.SelectContext(ctx, ds.reader(ctx), &hTemplates, stmt, hostUUID); err != nil { + return nil, err + } + return hTemplates, nil +} + +func (ds *Datastore) GetMDMProfileSummaryFromHostCertificateTemplates(ctx context.Context, teamID *uint) (*fleet.MDMProfilesSummary, error) { + var stmt string + var args []interface{} + + if teamID != nil && *teamID > 0 { + stmt = ` +SELECT + hct.status AS status, + COUNT(DISTINCT hct.host_uuid) AS n +FROM host_certificate_templates hct +INNER JOIN certificate_templates ct ON hct.certificate_template_id = ct.id +WHERE ct.team_id = ? +GROUP BY 1` + args = append(args, *teamID) + } else { + stmt = ` +SELECT + hct.status AS status, + COUNT(DISTINCT hct.host_uuid) AS n +FROM host_certificate_templates hct +GROUP BY 1` + } + + var dest []struct { + Count uint `db:"n"` + Status string `db:"status"` + } + + if err := sqlx.SelectContext(ctx, ds.reader(ctx), &dest, stmt, args...); err != nil { + return nil, err + } + + byStatus := make(map[string]uint) + for _, s := range dest { + if _, ok := byStatus[s.Status]; ok { + return nil, fmt.Errorf("duplicate status %s found", s.Status) + } + byStatus[s.Status] = s.Count + } + + var res fleet.MDMProfilesSummary + for s, c := range byStatus { + switch fleet.MDMDeliveryStatus(s) { + case fleet.MDMDeliveryFailed: + res.Failed = c + case fleet.MDMDeliveryPending: + res.Pending = c + case fleet.MDMDeliveryVerifying: + res.Verifying = c + case fleet.MDMDeliveryVerified: + res.Verified = c + default: + return nil, fmt.Errorf("unknown status %s", s) + } + } + + return &res, nil +} diff --git a/server/datastore/mysql/certificate_templates_test.go b/server/datastore/mysql/certificate_templates_test.go index ca47a5b7e2..f23b2daef3 100644 --- a/server/datastore/mysql/certificate_templates_test.go +++ b/server/datastore/mysql/certificate_templates_test.go @@ -3,10 +3,13 @@ package mysql import ( "context" "fmt" + "sort" "testing" + "time" "github.com/fleetdm/fleet/v4/server/fleet" "github.com/fleetdm/fleet/v4/server/ptr" + "github.com/fleetdm/fleet/v4/server/test" "github.com/google/uuid" "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" @@ -25,6 +28,8 @@ func TestCertificates(t *testing.T) { {"DeleteCertificateTemplate", testDeleteCertificateTemplate}, {"BatchUpsertCertificates", testBatchUpsertCertificates}, {"BatchDeleteCertificateTemplates", testBatchDeleteCertificateTemplates}, + {"GetHostCertificateTemplates", testGetHostCertificateTemplates}, + {"GetMDMProfileSummaryFromHostCertificateTemplates", testGetMDMProfileSummaryFromHostCertificateTemplates}, } for _, c := range cases { @@ -780,3 +785,218 @@ WHERE host_uuid = ? AND certificate_template_id = ?; }) } } + +func testGetHostCertificateTemplates(t *testing.T, ds *Datastore) { + defer TruncateTables(t, ds) + + ctx := context.Background() + + h1 := test.NewHost(t, ds, "host_1", "127.0.0.1", "1", "1", time.Now()) + h2 := test.NewHost(t, ds, "host_2", "127.0.0.2", "2", "2", time.Now()) + + team, err := ds.NewTeam(ctx, &fleet.Team{Name: "Test Team"}) + require.NoError(t, err) + + h2.TeamID = &team.ID + err = ds.UpdateHost(ctx, h2) + require.NoError(t, err) + + // Create a test certificate authority + ca, err := ds.NewCertificateAuthority(ctx, &fleet.CertificateAuthority{ + Type: string(fleet.CATypeCustomSCEPProxy), + Name: ptr.String("Test SCEP CA"), + URL: ptr.String("http://localhost:8080/scep"), + Challenge: ptr.String("test-challenge"), + }) + require.NoError(t, err) + + // Create some certificate templates + ct1, err := ds.CreateCertificateTemplate(ctx, &fleet.CertificateTemplate{ + Name: "AAA", + TeamID: team.ID, + CertificateAuthorityID: ca.ID, + SubjectName: "CN=Test Subject 1", + }) + require.NoError(t, err) + + ct2, err := ds.CreateCertificateTemplate(ctx, &fleet.CertificateTemplate{ + Name: "BBB", + TeamID: team.ID, + CertificateAuthorityID: ca.ID, + SubjectName: "CN=Test Subject 2", + }) + require.NoError(t, err) + + // Set the installation status on the certificate templates + // TODO: Refactor this to use UpdateStatus DB method when available + _, err = ds.writer(ctx).ExecContext(ctx, + "INSERT INTO host_certificate_templates (host_uuid, certificate_template_id, fleet_challenge, status) VALUES (?, ?, ?, ?)", + h2.UUID, ct1.ID, "test-challenge", fleet.OSSettingsVerified, + ) + + require.NoError(t, err) + _, err = ds.writer(ctx).ExecContext(ctx, + "INSERT INTO host_certificate_templates (host_uuid, certificate_template_id, fleet_challenge, status) VALUES (?, ?, ?, ?)", + h2.UUID, ct2.ID, "test-challenge", fleet.OSSettingsFailed, + ) + require.NoError(t, err) + + testCases := []struct { + name string + do func(*testing.T, *Datastore) + }{ + { + "hostUUID is not provided", + func(t *testing.T, ds *Datastore) { + _, err := ds.GetHostCertificateTemplates(ctx, "") + require.Error(t, err) + }, + }, + + { + "No certificate templates found", + func(t *testing.T, ds *Datastore) { + templates, err := ds.GetHostCertificateTemplates(ctx, h1.UUID) + require.NoError(t, err) + require.Empty(t, templates) + }, + }, + { + "Returns the certificates available for the host", + func(t *testing.T, datastore *Datastore) { + templates, err := ds.GetHostCertificateTemplates(ctx, h2.UUID) + require.NoError(t, err) + require.Len(t, templates, 2) + + // Sort the templates by name to make results deterministic + sort.Slice(templates, func(i, j int) bool { return templates[i].Name < templates[j].Name }) + + require.Equal(t, ct1.Name, templates[0].Name) + require.Equal(t, fleet.MDMDeliveryVerified, templates[0].Status) + + require.Equal(t, ct2.Name, templates[1].Name) + require.Equal(t, fleet.MDMDeliveryFailed, templates[1].Status) + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + tc.do(t, ds) + }) + } +} + +func testGetMDMProfileSummaryFromHostCertificateTemplates(t *testing.T, ds *Datastore) { + defer TruncateTables(t, ds) + + ctx := context.Background() + + // Doing this with no data in the table should be ok + result, err := ds.GetMDMProfileSummaryFromHostCertificateTemplates(ctx, nil) + require.NoError(t, err) + require.Equal(t, uint(0), result.Pending) + require.Equal(t, uint(0), result.Failed) + require.Equal(t, uint(0), result.Verified) + require.Equal(t, uint(0), result.Verifying) + + h1 := test.NewHost(t, ds, "host_1", "127.0.0.1", "1", "1", time.Now()) + h2 := test.NewHost(t, ds, "host_2", "127.0.0.2", "2", "2", time.Now()) + + team1, err := ds.NewTeam(ctx, &fleet.Team{Name: "Team 1"}) + require.NoError(t, err) + + team2, err := ds.NewTeam(ctx, &fleet.Team{Name: "Team 2"}) + require.NoError(t, err) + + h1.TeamID = &team1.ID + err = ds.UpdateHost(ctx, h2) + require.NoError(t, err) + + h2.TeamID = &team2.ID + err = ds.UpdateHost(ctx, h2) + require.NoError(t, err) + + // Create a test certificate authority + ca, err := ds.NewCertificateAuthority(ctx, &fleet.CertificateAuthority{ + Type: string(fleet.CATypeCustomSCEPProxy), + Name: ptr.String("Test SCEP CA"), + URL: ptr.String("http://localhost:8080/scep"), + Challenge: ptr.String("test-challenge"), + }) + require.NoError(t, err) + + // Create some certificate templates + ct1, err := ds.CreateCertificateTemplate(ctx, &fleet.CertificateTemplate{ + Name: "AAA", + TeamID: team1.ID, + CertificateAuthorityID: ca.ID, + SubjectName: "CN=Test Subject 1", + }) + require.NoError(t, err) + + ct2, err := ds.CreateCertificateTemplate(ctx, &fleet.CertificateTemplate{ + Name: "BBB", + TeamID: team2.ID, + CertificateAuthorityID: ca.ID, + SubjectName: "CN=Test Subject 2", + }) + require.NoError(t, err) + + // Set the installation status on the certificate templates + // TODO: Refactor this to use UpdateStatus DB method when available + _, err = ds.writer(ctx).ExecContext(ctx, + "INSERT INTO host_certificate_templates (host_uuid, certificate_template_id, fleet_challenge, status) VALUES (?, ?, ?, ?)", + h1.UUID, ct1.ID, "test-challenge", fleet.OSSettingsPending, + ) + require.NoError(t, err) + + _, err = ds.writer(ctx).ExecContext(ctx, + "INSERT INTO host_certificate_templates (host_uuid, certificate_template_id, fleet_challenge, status) VALUES (?, ?, ?, ?)", + h2.UUID, ct1.ID, "test-challenge", fleet.OSSettingsVerified, + ) + require.NoError(t, err) + + _, err = ds.writer(ctx).ExecContext(ctx, + "INSERT INTO host_certificate_templates (host_uuid, certificate_template_id, fleet_challenge, status) VALUES (?, ?, ?, ?)", + h2.UUID, ct2.ID, "test-challenge", fleet.OSSettingsFailed, + ) + require.NoError(t, err) + + testCases := []struct { + name string + do func(*testing.T, *Datastore) + }{ + { + "no teamID provided", + func(t *testing.T, ds *Datastore) { + result, err := ds.GetMDMProfileSummaryFromHostCertificateTemplates(ctx, nil) + + require.NoError(t, err) + require.NotNil(t, result) + require.Equal(t, uint(1), result.Pending) + require.Equal(t, uint(1), result.Verified) + require.Equal(t, uint(1), result.Failed) + }, + }, + { + "teamID provided", + func(t *testing.T, ds *Datastore) { + result, err := ds.GetMDMProfileSummaryFromHostCertificateTemplates(ctx, &team1.ID) + + require.NoError(t, err) + require.NotNil(t, result) + + require.Equal(t, uint(0), result.Failed) + require.Equal(t, uint(1), result.Verified) + require.Equal(t, uint(1), result.Pending) + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + tc.do(t, ds) + }) + } +} diff --git a/server/fleet/certificate_templates.go b/server/fleet/certificate_templates.go index a60abd9830..fc7b145d1c 100644 --- a/server/fleet/certificate_templates.go +++ b/server/fleet/certificate_templates.go @@ -31,3 +31,24 @@ type CertificateTemplateResponseFull struct { SubjectName string `json:"subject_name" db:"subject_name"` TeamID uint `json:"-" db:"team_id"` } + +// HostCertificateTemplate represents a certificate template associated with a particular host +type HostCertificateTemplate struct { + HostUUID string `db:"host_uuid" json:"-"` + Name string `db:"name" json:"-"` + Status MDMDeliveryStatus `db:"status" json:"-"` +} + +// ToHostMDMProfile maps a HostCertificateTemplate to a HostMDMProfile, suitable for use in the MDM API +func (p *HostCertificateTemplate) ToHostMDMProfile() HostMDMProfile { + if p == nil { + return HostMDMProfile{} + } + + return HostMDMProfile{ + HostUUID: p.HostUUID, + Name: p.Name, + Platform: "android", + Status: &p.Status, + } +} diff --git a/server/fleet/certificate_templates_test.go b/server/fleet/certificate_templates_test.go new file mode 100644 index 0000000000..3415ed3ff4 --- /dev/null +++ b/server/fleet/certificate_templates_test.go @@ -0,0 +1,49 @@ +package fleet + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestHostCertificateTemplate(t *testing.T) { + t.Run("ToHostMDMProfile", func(t *testing.T) { + tests := []struct { + name string + template *HostCertificateTemplate + expectation func(*testing.T, HostMDMProfile) + }{ + { + name: "nil template", + template: nil, + expectation: func(t *testing.T, profile HostMDMProfile) { + require.Equal(t, "", profile.HostUUID) + require.Equal(t, "", profile.Name) + require.Equal(t, "", profile.Platform) + require.Nil(t, profile.Status) + }, + }, + { + name: "maps fields correctly", + template: &HostCertificateTemplate{ + HostUUID: "1234", + Name: "HostCertificate", + Status: MDMDeliveryVerified, + }, + expectation: func(t *testing.T, profile HostMDMProfile) { + require.Equal(t, "1234", profile.HostUUID) + require.Equal(t, "HostCertificate", profile.Name) + require.Equal(t, "android", profile.Platform) + require.Equal(t, MDMDeliveryVerified, *profile.Status) + + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.expectation(t, tt.template.ToHostMDMProfile()) + }) + } + }) +} diff --git a/server/fleet/datastore.go b/server/fleet/datastore.go index 9a1fadd94f..2c5decdb94 100644 --- a/server/fleet/datastore.go +++ b/server/fleet/datastore.go @@ -2338,6 +2338,13 @@ type Datastore interface { // assigned to any team). GetMDMAndroidProfilesSummary(ctx context.Context, teamID *uint) (*MDMProfilesSummary, error) + // GetCertificateStatusSummary GetCertificateTemplatesSummary returns a summary of the current state of certificate templates on each host in + // the specified team (or, if no team is specified, each host that is not assigned to any team). + GetMDMProfileSummaryFromHostCertificateTemplates(ctx context.Context, teamID *uint) (*MDMProfilesSummary, error) + + // GetHostCertificateTemplates returns what certificate templates are currently associated with the specified host. + GetHostCertificateTemplates(ctx context.Context, hostUUID string) ([]HostCertificateTemplate, error) + // GetHostMDMAndroidProfiles retrieves the Android MDM profiles for a specific host. GetHostMDMAndroidProfiles(ctx context.Context, hostUUID string) ([]HostMDMAndroidProfile, error) diff --git a/server/fleet/mdm.go b/server/fleet/mdm.go index 22caf3b6a1..1a66c75d33 100644 --- a/server/fleet/mdm.go +++ b/server/fleet/mdm.go @@ -405,14 +405,14 @@ type MDMDiskEncryptionSummary struct { } // 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. +// profiles, disk encryption or certificate templates. +// 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 + // Verified includes each host where Fleet has verified the installation of all the // profiles currently applicable to the host. If any of the profiles are pending, failed, or // subject to verification for the host, the host is not counted as verified. Verified uint `json:"verified" db:"verified"` - // Verifying includes each host where the MDM service has successfully delivered all of the + // Verifying includes each host where the MDM service has successfully delivered all the // profiles currently applicable to the host. If any of the profiles are pending or failed for // the host, the host is not counted as verifying. Verifying uint `json:"verifying" db:"verifying"` @@ -424,6 +424,23 @@ type MDMProfilesSummary struct { Failed uint `json:"failed" db:"failed"` } +func (mdmPS *MDMProfilesSummary) Add(other *MDMProfilesSummary) *MDMProfilesSummary { + var s1, s2 MDMProfilesSummary + if mdmPS != nil { + s1 = *mdmPS + } + if other != nil { + s2 = *other + } + return &MDMProfilesSummary{ + Verified: s1.Verified + s2.Verified, + Verifying: s1.Verifying + s2.Verifying, + Pending: s1.Pending + s2.Pending, + Failed: s1.Failed + s2.Failed, + } + +} + // HostMDMProfile is the status of an MDM profile on a host. It can be used to represent either // a Windows or macOS profile. type HostMDMProfile struct { diff --git a/server/fleet/mdm_test.go b/server/fleet/mdm_test.go index 229554983f..0d7c5921d0 100644 --- a/server/fleet/mdm_test.go +++ b/server/fleet/mdm_test.go @@ -717,3 +717,68 @@ func TestFilterOutUserScopedProfiles(t *testing.T) { require.ElementsMatch(t, filteredProfiles, []*fleet.MDMAppleProfilePayload{&systemScopedProfile}) } + +func TestMDMProfilesSummary(t *testing.T) { + t.Run("Add", func(t *testing.T) { + tests := []struct { + name string + a *fleet.MDMProfilesSummary + b *fleet.MDMProfilesSummary + expected *fleet.MDMProfilesSummary + }{ + { + name: "Both nil", + a: nil, + b: nil, + expected: &fleet.MDMProfilesSummary{Verified: 0, Verifying: 0, Pending: 0, Failed: 0}, + }, + { + name: "First nil", + a: nil, + b: &fleet.MDMProfilesSummary{Verified: 1, Verifying: 2, Pending: 3, Failed: 4}, + expected: &fleet.MDMProfilesSummary{Verified: 1, Verifying: 2, Pending: 3, Failed: 4}, + }, + { + name: "Second nil", + a: &fleet.MDMProfilesSummary{Verified: 1, Verifying: 2, Pending: 3, Failed: 4}, + b: nil, + expected: &fleet.MDMProfilesSummary{Verified: 1, Verifying: 2, Pending: 3, Failed: 4}, + }, + { + name: "Combined values", + a: &fleet.MDMProfilesSummary{Verified: 2, Verifying: 4, Pending: 6, Failed: 8}, + b: &fleet.MDMProfilesSummary{Verified: 1, Verifying: 3, Pending: 5, Failed: 7}, + expected: &fleet.MDMProfilesSummary{ + Verified: 3, + Verifying: 7, + Pending: 11, + Failed: 15, + }, + }, + { + name: "All zero values", + a: &fleet.MDMProfilesSummary{Verified: 0, Verifying: 0, Pending: 0, Failed: 0}, + b: &fleet.MDMProfilesSummary{Verified: 0, Verifying: 0, Pending: 0, Failed: 0}, + expected: &fleet.MDMProfilesSummary{Verified: 0, Verifying: 0, Pending: 0, Failed: 0}, + }, + { + name: "Large values", + a: &fleet.MDMProfilesSummary{Verified: 1_000_000, Verifying: 2_000_000, Pending: 3_000_000, Failed: 4_000_000}, + b: &fleet.MDMProfilesSummary{Verified: 5_000_000, Verifying: 6_000_000, Pending: 7_000_000, Failed: 8_000_000}, + expected: &fleet.MDMProfilesSummary{ + Verified: 6_000_000, + Verifying: 8_000_000, + Pending: 10_000_000, + Failed: 12_000_000, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.a.Add(tt.b) + require.Equal(t, tt.expected, result) + }) + } + }) +} diff --git a/server/mock/datastore_mock.go b/server/mock/datastore_mock.go index e4fa9ce0c5..da497bbb4f 100644 --- a/server/mock/datastore_mock.go +++ b/server/mock/datastore_mock.go @@ -1515,6 +1515,10 @@ type DeleteMDMAndroidConfigProfileFunc func(ctx context.Context, profileUUID str type GetMDMAndroidProfilesSummaryFunc func(ctx context.Context, teamID *uint) (*fleet.MDMProfilesSummary, error) +type GetMDMProfileSummaryFromHostCertificateTemplatesFunc func(ctx context.Context, teamID *uint) (*fleet.MDMProfilesSummary, error) + +type GetHostCertificateTemplatesFunc func(ctx context.Context, hostUUID string) ([]fleet.HostCertificateTemplate, error) + type GetHostMDMAndroidProfilesFunc func(ctx context.Context, hostUUID string) ([]fleet.HostMDMAndroidProfile, error) type NewAndroidPolicyRequestFunc func(ctx context.Context, req *fleet.MDMAndroidPolicyRequest) error @@ -3874,6 +3878,12 @@ type DataStore struct { GetMDMAndroidProfilesSummaryFunc GetMDMAndroidProfilesSummaryFunc GetMDMAndroidProfilesSummaryFuncInvoked bool + GetMDMProfileSummaryFromHostCertificateTemplatesFunc GetMDMProfileSummaryFromHostCertificateTemplatesFunc + GetMDMProfileSummaryFromHostCertificateTemplatesFuncInvoked bool + + GetHostCertificateTemplatesFunc GetHostCertificateTemplatesFunc + GetHostCertificateTemplatesFuncInvoked bool + GetHostMDMAndroidProfilesFunc GetHostMDMAndroidProfilesFunc GetHostMDMAndroidProfilesFuncInvoked bool @@ -9279,6 +9289,20 @@ func (s *DataStore) GetMDMAndroidProfilesSummary(ctx context.Context, teamID *ui return s.GetMDMAndroidProfilesSummaryFunc(ctx, teamID) } +func (s *DataStore) GetMDMProfileSummaryFromHostCertificateTemplates(ctx context.Context, teamID *uint) (*fleet.MDMProfilesSummary, error) { + s.mu.Lock() + s.GetMDMProfileSummaryFromHostCertificateTemplatesFuncInvoked = true + s.mu.Unlock() + return s.GetMDMProfileSummaryFromHostCertificateTemplatesFunc(ctx, teamID) +} + +func (s *DataStore) GetHostCertificateTemplates(ctx context.Context, hostUUID string) ([]fleet.HostCertificateTemplate, error) { + s.mu.Lock() + s.GetHostCertificateTemplatesFuncInvoked = true + s.mu.Unlock() + return s.GetHostCertificateTemplatesFunc(ctx, hostUUID) +} + func (s *DataStore) GetHostMDMAndroidProfiles(ctx context.Context, hostUUID string) ([]fleet.HostMDMAndroidProfile, error) { s.mu.Lock() s.GetHostMDMAndroidProfilesFuncInvoked = true diff --git a/server/service/hosts.go b/server/service/hosts.go index 0ded97b4f2..ba9fbaff53 100644 --- a/server/service/hosts.go +++ b/server/service/hosts.go @@ -1363,6 +1363,14 @@ func (svc *Service) getHostDetails(ctx context.Context, host *fleet.Host, opts f profiles = append(profiles, p.ToHostMDMProfile()) } + hCertTemplates, err := svc.ds.GetHostCertificateTemplates(ctx, host.UUID) + if err != nil { + return nil, ctxerr.Wrap(ctx, err, "get host certificate templates") + } + for _, ct := range hCertTemplates { + profiles = append(profiles, ct.ToHostMDMProfile()) + } + case "darwin", "ios", "ipados": if ac.MDM.EnabledAndConfigured { profs, err := svc.ds.GetHostMDMAppleProfiles(ctx, host.UUID) diff --git a/server/service/mdm.go b/server/service/mdm.go index 661f4cbd4b..62cfd40233 100644 --- a/server/service/mdm.go +++ b/server/service/mdm.go @@ -1032,7 +1032,12 @@ func (svc *Service) GetMDMAndroidProfilesSummary(ctx context.Context, teamID *ui return nil, ctxerr.Wrap(ctx, err) } - return ps, nil + hcts, err := svc.ds.GetMDMProfileSummaryFromHostCertificateTemplates(ctx, teamID) + if err != nil { + return nil, ctxerr.Wrap(ctx, err) + } + + return ps.Add(hcts), nil } // authorizeAllHostsTeams is a helper function that loads the hosts @@ -2443,7 +2448,6 @@ func getAndroidProfiles(ctx context.Context, appCfg *fleet.AppConfig, profiles map[int]fleet.MDMProfileBatchPayload, labelMap map[string]fleet.ConfigurationProfileLabel, - // isPremium bool, ) (map[int]*fleet.MDMAndroidConfigProfile, error) { profs := make(map[int]*fleet.MDMAndroidConfigProfile, len(profiles)) for i, profile := range profiles { diff --git a/server/service/mdm_test.go b/server/service/mdm_test.go index ffce1478a1..c2252c8fa6 100644 --- a/server/service/mdm_test.go +++ b/server/service/mdm_test.go @@ -676,6 +676,9 @@ func TestMDMCommonAuthorization(t *testing.T) { ds.GetConfigEnableDiskEncryptionFunc = func(ctx context.Context, teamID *uint) (fleet.DiskEncryptionConfig, error) { return fleet.DiskEncryptionConfig{}, nil } + ds.GetMDMProfileSummaryFromHostCertificateTemplatesFunc = func(ctx context.Context, teamID *uint) (*fleet.MDMProfilesSummary, error) { + return &fleet.MDMProfilesSummary{}, nil + } ds.AreHostsConnectedToFleetMDMFunc = func(ctx context.Context, hosts []*fleet.Host) (map[string]bool, error) { res := make(map[string]bool, len(hosts))