>(
},
Filter: DefaultColumnFilter,
disableSortBy: false,
- sortType: sortType(colName, osqueryTableColumns),
+ sortType: getSortTypeFromColumnType(colName, tableColumns),
};
});
return _unshiftHostname(columnsConfigs);
diff --git a/frontend/services/entities/mdm.ts b/frontend/services/entities/mdm.ts
index 90101a1135..eb580d06f6 100644
--- a/frontend/services/entities/mdm.ts
+++ b/frontend/services/entities/mdm.ts
@@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import {
DiskEncryptionStatus,
+ IHostMdmProfile,
IMdmProfile,
MdmProfileStatus,
} from "interfaces/mdm";
@@ -47,6 +48,10 @@ export interface IUploadProfileApiParams {
labels?: string[];
}
+export const isDDMProfile = (profile: IMdmProfile | IHostMdmProfile) => {
+ return profile.profile_uuid.startsWith("d");
+};
+
interface IUpdateSetupExperienceBody {
team_id?: number;
enable_release_device_manually: boolean;
diff --git a/frontend/utilities/helpers.tsx b/frontend/utilities/helpers.tsx
index a7ece58a81..2b9dc59ba4 100644
--- a/frontend/utilities/helpers.tsx
+++ b/frontend/utilities/helpers.tsx
@@ -2,6 +2,7 @@ import React from "react";
import {
isEmpty,
flatMap,
+ find,
omit,
pick,
size,
@@ -25,6 +26,7 @@ import { buildQueryStringFromParams } from "utilities/url";
import { IHost } from "interfaces/host";
import { ILabel } from "interfaces/label";
import { IPack } from "interfaces/pack";
+import { IQueryTableColumn } from "interfaces/osquery_table";
import {
IScheduledQuery,
IPackQueryFormData,
@@ -39,6 +41,8 @@ import { UserRole } from "interfaces/user";
import stringUtils from "utilities/strings";
import sortUtils from "utilities/sort";
+import { checkTable } from "utilities/sql_tools";
+import { osqueryTables } from "utilities/osquery_tables";
import {
DEFAULT_EMPTY_CELL_VALUE,
DEFAULT_GRAVATAR_LINK,
@@ -887,6 +891,40 @@ export const getUniqueColumnNamesFromRows = <
// can allow additional dropdown value types in the future
type DropdownOptionValue = IDropdownOption["value"];
+/** Generates the column schema for a sql query */
+export const getTableColumnsFromSql = (
+ sql: string
+): IQueryTableColumn[] | [] => {
+ const tableNames = (sql && checkTable(sql).tables) || [];
+
+ let sqlColumns: IQueryTableColumn[] | [] = [];
+ tableNames.forEach((tableName: string) => {
+ const tableColumns =
+ find(osqueryTables, { name: tableName })?.columns || [];
+ sqlColumns = [...sqlColumns, ...tableColumns];
+ });
+ // TODO: Edge case of tables sharing column names with different typing not considered
+
+ return sqlColumns;
+};
+
+/** Sorts sql results numerical columns correctly while perserving case insensitive sort for text columns */
+export const getSortTypeFromColumnType = (
+ colName: string | number | symbol,
+ tableColumns?: IQueryTableColumn[] | []
+) => {
+ if (typeof colName === "string") {
+ const numberTypes = ["integer", "bigint", "unsigned_bigint", "double"];
+
+ const type = find(tableColumns, { name: colName })?.type;
+
+ if (type && numberTypes.includes(type)) {
+ return "alphanumeric";
+ }
+ }
+ return "caseInsensitive";
+};
+
export function getCustomDropdownOptions(
defaultOptions: IDropdownOption[],
customValue: DropdownOptionValue,
@@ -918,6 +956,8 @@ export default {
generateRole,
generateTeam,
getUniqueColumnNamesFromRows,
+ getTableColumnsFromSql,
+ getSortTypeFromColumnType,
getCustomDropdownOptions,
greyCell,
humanHostLastSeen,
diff --git a/infrastructure/dogfood/terraform/aws/variables.tf b/infrastructure/dogfood/terraform/aws/variables.tf
index 2775e36c4e..991c9135de 100644
--- a/infrastructure/dogfood/terraform/aws/variables.tf
+++ b/infrastructure/dogfood/terraform/aws/variables.tf
@@ -56,7 +56,7 @@ variable "database_name" {
variable "fleet_image" {
description = "the name of the container image to run"
- default = "fleetdm/fleet:v4.47.2"
+ default = "fleetdm/fleet:v4.47.3"
}
variable "software_inventory" {
diff --git a/infrastructure/dogfood/terraform/gcp/variables.tf b/infrastructure/dogfood/terraform/gcp/variables.tf
index 98f012a0cd..b3cddde5ca 100644
--- a/infrastructure/dogfood/terraform/gcp/variables.tf
+++ b/infrastructure/dogfood/terraform/gcp/variables.tf
@@ -68,5 +68,5 @@ variable "redis_mem" {
}
variable "image" {
- default = "fleet:v4.47.2"
+ default = "fleet:v4.47.3"
}
diff --git a/orbit/pkg/packaging/windows.go b/orbit/pkg/packaging/windows.go
index 731e57efe0..b35a00c5bb 100644
--- a/orbit/pkg/packaging/windows.go
+++ b/orbit/pkg/packaging/windows.go
@@ -222,7 +222,7 @@ func checkWine(wineChecked bool) error {
cmd := exec.Command(wix.WineCmd, "--version")
if err := cmd.Run(); err != nil {
return fmt.Errorf(
- "%s failed. Is Wine installed? Creating a fleetd agent for Windows (.msi) requires Wine. To install Wine see the script here: https://github.com/fleetdm/fleet/blob/fleet-v4.44.0/scripts/macos-install-wine.sh %w",
+ "%s failed. Is Wine installed? Creating a fleetd agent for Windows (.msi) requires Wine. To install Wine see the script here: https://fleetdm.com/install-wine %w",
wix.WineCmd, err,
)
}
diff --git a/pkg/mdm/mdmtest/apple.go b/pkg/mdm/mdmtest/apple.go
index 0f15c7c453..deb52664b7 100644
--- a/pkg/mdm/mdmtest/apple.go
+++ b/pkg/mdm/mdmtest/apple.go
@@ -9,6 +9,7 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/base64"
+ "encoding/json"
"errors"
"fmt"
"io"
@@ -21,6 +22,7 @@ import (
"time"
"github.com/fleetdm/fleet/v4/pkg/fleethttp"
+ "github.com/fleetdm/fleet/v4/server/fleet"
apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple"
"github.com/fleetdm/fleet/v4/server/mdm/nanomdm/mdm"
"github.com/fleetdm/fleet/v4/server/mdm/scep/cryptoutil/x509util"
@@ -31,7 +33,6 @@ import (
httptransport "github.com/go-kit/kit/transport/http"
"github.com/google/uuid"
"github.com/groob/plist"
- micromdm "github.com/micromdm/micromdm/mdm/mdm"
"go.mozilla.org/pkcs7"
)
@@ -386,6 +387,30 @@ func (c *TestAppleMDMClient) TokenUpdate() error {
return err
}
+// DeclarativeManagement sends a DeclarativeManagement checkin request to the server.
+//
+// The endpoint argument is used as the value for the `Endpoint` key in the request payload.
+//
+// For more details check https://developer.apple.com/documentation/devicemanagement/declarativemanagementrequest
+func (c *TestAppleMDMClient) DeclarativeManagement(endpoint string, data ...fleet.MDMAppleDDMStatusReport) (*http.Response, error) {
+ payload := map[string]any{
+ "MessageType": "DeclarativeManagement",
+ "UDID": c.UUID,
+ "Topic": "com.apple.mgmt.External." + c.UUID,
+ "EnrollmentID": "testenrollmentid-" + c.UUID,
+ "Endpoint": endpoint,
+ }
+ if len(data) != 0 {
+ rawData, err := json.Marshal(data[0])
+ if err != nil {
+ return nil, fmt.Errorf("marshaling status report: %w", err)
+ }
+ payload["Data"] = rawData
+ }
+ r, err := c.request("application/x-apple-aspen-mdm-checkin", payload)
+ return r, err
+}
+
// Checkout sends the CheckOut message to the MDM server.
func (c *TestAppleMDMClient) Checkout() error {
payload := map[string]any{
@@ -404,7 +429,7 @@ func (c *TestAppleMDMClient) Checkout() error {
// receive commands. The server can signal back with either a command to run
// or an empty (nil, nil) response body to end the communication
// (i.e. no commands to run).
-func (c *TestAppleMDMClient) Idle() (*micromdm.CommandPayload, error) {
+func (c *TestAppleMDMClient) Idle() (*mdm.Command, error) {
payload := map[string]any{
"Status": "Idle",
"Topic": "com.apple.mgmt.External." + c.UUID,
@@ -420,7 +445,7 @@ func (c *TestAppleMDMClient) Idle() (*micromdm.CommandPayload, error) {
// The server can signal back with either a command to run
// or an empty (nil, nil) response body to end the communication
// (i.e. no commands to run).
-func (c *TestAppleMDMClient) Acknowledge(cmdUUID string) (*micromdm.CommandPayload, error) {
+func (c *TestAppleMDMClient) Acknowledge(cmdUUID string) (*mdm.Command, error) {
payload := map[string]any{
"Status": "Acknowledged",
"Topic": "com.apple.mgmt.External." + c.UUID,
@@ -473,7 +498,7 @@ func (c *TestAppleMDMClient) GetBootstrapToken() ([]byte, error) {
// The server can signal back with either a command to run
// or an empty (nil, nil) response body to end the communication
// (i.e. no commands to run).
-func (c *TestAppleMDMClient) Err(cmdUUID string, errChain []mdm.ErrorChain) (*micromdm.CommandPayload, error) {
+func (c *TestAppleMDMClient) Err(cmdUUID string, errChain []mdm.ErrorChain) (*mdm.Command, error) {
payload := map[string]any{
"Status": "Error",
"Topic": "com.apple.mgmt.External." + c.UUID,
@@ -485,7 +510,7 @@ func (c *TestAppleMDMClient) Err(cmdUUID string, errChain []mdm.ErrorChain) (*mi
return c.sendAndDecodeCommandResponse(payload)
}
-func (c *TestAppleMDMClient) sendAndDecodeCommandResponse(payload map[string]any) (*micromdm.CommandPayload, error) {
+func (c *TestAppleMDMClient) sendAndDecodeCommandResponse(payload map[string]any) (*mdm.Command, error) {
res, err := c.request("", payload)
if err != nil {
return nil, fmt.Errorf("request error: %w", err)
@@ -510,11 +535,12 @@ func (c *TestAppleMDMClient) sendAndDecodeCommandResponse(payload map[string]any
if err != nil {
return nil, fmt.Errorf("decode command: %w", err)
}
- var p micromdm.CommandPayload
+ var p mdm.Command
err = plist.Unmarshal(cmd.Raw, &p)
if err != nil {
return nil, fmt.Errorf("unmarshal command payload: %w", err)
}
+ p.Raw = cmd.Raw
return &p, nil
}
diff --git a/scripts/macos-install-wine.sh b/scripts/macos-install-wine.sh
index fd5c55532b..5a29f352d2 100755
--- a/scripts/macos-install-wine.sh
+++ b/scripts/macos-install-wine.sh
@@ -2,8 +2,9 @@
set -eo pipefail
+# Run this script in user context (not root).
# Reference: https://wiki.winehq.org/MacOS
-# NOTE: Wine can be installed without brew via a distribution such as https://github.com/Gcenx/macOS_Wine_builds/releases/tag/9.0, or by building from source.
+# Wine can be installed without brew via a distribution such as https://github.com/Gcenx/macOS_Wine_builds/releases/tag/9.0, or by building from source.
# Check if brew is installed
if ! command -v brew >/dev/null 2>&1 ; then
diff --git a/server/datastore/mysql/apple_mdm.go b/server/datastore/mysql/apple_mdm.go
index 61f1979db2..b22f178300 100644
--- a/server/datastore/mysql/apple_mdm.go
+++ b/server/datastore/mysql/apple_mdm.go
@@ -16,8 +16,8 @@ import (
"github.com/fleetdm/fleet/v4/server/mdm/nanodep/godep"
"github.com/fleetdm/fleet/v4/server/mdm/nanomdm/mdm"
"github.com/fleetdm/fleet/v4/server/ptr"
- "github.com/go-kit/kit/log"
- "github.com/go-kit/kit/log/level"
+ "github.com/go-kit/log"
+ "github.com/go-kit/log/level"
"github.com/google/uuid"
"github.com/jmoiron/sqlx"
)
@@ -30,6 +30,8 @@ INSERT INTO
(SELECT ?, ?, ?, ?, ?, UNHEX(MD5(?)), CURRENT_TIMESTAMP() FROM DUAL WHERE
NOT EXISTS (
SELECT 1 FROM mdm_windows_configuration_profiles WHERE name = ? AND team_id = ?
+ ) AND NOT EXISTS (
+ SELECT 1 FROM mdm_apple_declarations WHERE name = ? AND team_id = ?
)
)`
@@ -41,7 +43,7 @@ INSERT INTO
var profileID int64
err := ds.withTx(ctx, func(tx sqlx.ExtContext) error {
res, err := tx.ExecContext(ctx, stmt,
- profUUID, teamID, cp.Identifier, cp.Name, cp.Mobileconfig, cp.Mobileconfig, cp.Name, teamID)
+ profUUID, teamID, cp.Identifier, cp.Name, cp.Mobileconfig, cp.Mobileconfig, cp.Name, teamID, cp.Name, teamID)
if err != nil {
switch {
case isDuplicate(err):
@@ -106,6 +108,25 @@ func formatErrorDuplicateConfigProfile(err error, cp *fleet.MDMAppleConfigProfil
}
}
+func formatErrorDuplicateDeclaration(err error, decl *fleet.MDMAppleDeclaration) error {
+ switch {
+ case strings.Contains(err.Error(), "idx_mdm_apple_declaration_team_identifier"):
+ return &existsError{
+ ResourceType: "MDMAppleDeclaration.Identifier",
+ Identifier: decl.Identifier,
+ TeamID: decl.TeamID,
+ }
+ case strings.Contains(err.Error(), "idx_mdm_apple_declaration_team_name"):
+ return &existsError{
+ ResourceType: "MDMAppleDeclaration.Name",
+ Identifier: decl.Name,
+ TeamID: decl.TeamID,
+ }
+ default:
+ return err
+ }
+}
+
func (ds *Datastore) ListMDMAppleConfigProfiles(ctx context.Context, teamID *uint) ([]*fleet.MDMAppleConfigProfile, error) {
stmt := `
SELECT
@@ -192,7 +213,7 @@ WHERE
// get the labels for that profile, except if the profile was loaded by the
// old (deprecated) endpoint.
if uuid != "" {
- labels, err := ds.listProfileLabelsForProfiles(ctx, nil, []string{res.ProfileUUID})
+ labels, err := ds.listProfileLabelsForProfiles(ctx, nil, []string{res.ProfileUUID}, nil)
if err != nil {
return nil, err
}
@@ -205,11 +226,52 @@ WHERE
return &res, nil
}
+func (ds *Datastore) GetMDMAppleDeclaration(ctx context.Context, declUUID string) (*fleet.MDMAppleDeclaration, error) {
+ stmt := `
+SELECT
+ declaration_uuid,
+ team_id,
+ name,
+ identifier,
+ raw_json,
+ checksum,
+ created_at,
+ uploaded_at
+FROM
+ mdm_apple_declarations
+WHERE
+ declaration_uuid = ?`
+
+ var res fleet.MDMAppleDeclaration
+ err := sqlx.GetContext(ctx, ds.reader(ctx), &res, stmt, declUUID)
+ if err != nil {
+ if err == sql.ErrNoRows {
+ return nil, ctxerr.Wrap(ctx, notFound("MDMAppleDeclaration").WithName(declUUID))
+ }
+
+ return nil, ctxerr.Wrap(ctx, err, "get mdm apple declaration")
+ }
+
+ labels, err := ds.listProfileLabelsForProfiles(ctx, nil, nil, []string{res.DeclarationUUID})
+ if err != nil {
+ return nil, err
+ }
+ if len(labels) > 0 {
+ // ensure we leave Labels nil if there are none
+ res.Labels = labels
+ }
+
+ return &res, nil
+}
+
func (ds *Datastore) DeleteMDMAppleConfigProfileByDeprecatedID(ctx context.Context, profileID uint) error {
return ds.deleteMDMAppleConfigProfileByIDOrUUID(ctx, profileID, "")
}
func (ds *Datastore) DeleteMDMAppleConfigProfile(ctx context.Context, profileUUID string) error {
+ if strings.HasPrefix(profileUUID, fleet.MDMAppleDeclarationUUIDPrefix) {
+ return ds.deleteMDMAppleDeclaration(ctx, profileUUID)
+ }
return ds.deleteMDMAppleConfigProfileByIDOrUUID(ctx, 0, profileUUID)
}
@@ -239,11 +301,28 @@ func (ds *Datastore) deleteMDMAppleConfigProfileByIDOrUUID(ctx context.Context,
return nil
}
+func (ds *Datastore) deleteMDMAppleDeclaration(ctx context.Context, uuid string) error {
+ stmt := `DELETE FROM mdm_apple_declarations WHERE declaration_uuid = ?`
+
+ res, err := ds.writer(ctx).ExecContext(ctx, stmt, uuid)
+ if err != nil {
+ return ctxerr.Wrap(ctx, err)
+ }
+
+ deleted, _ := res.RowsAffected()
+ if deleted != 1 {
+ return ctxerr.Wrap(ctx, notFound("MDMAppleDeclaration").WithName(uuid))
+ }
+
+ return nil
+}
+
func (ds *Datastore) DeleteMDMAppleConfigProfileByTeamAndIdentifier(ctx context.Context, teamID *uint, profileIdentifier string) error {
if teamID == nil {
teamID = ptr.Uint(0)
}
+ // TODO: add deletion of declarations here or separate method?
res, err := ds.writer(ctx).ExecContext(ctx, `DELETE FROM mdm_apple_configuration_profiles WHERE team_id = ? AND identifier = ?`, teamID, profileIdentifier)
if err != nil {
return ctxerr.Wrap(ctx, err)
@@ -273,16 +352,38 @@ COALESCE(detail, '') AS detail
FROM
host_mdm_apple_profiles
WHERE
+host_uuid = ? AND NOT (operation_type = '%s' AND COALESCE(status, '%s') IN('%s', '%s'))
+
+UNION ALL
+SELECT
+declaration_uuid AS profile_uuid,
+declaration_name AS name,
+declaration_identifier AS identifier,
+-- internally, a NULL status implies that the cron needs to pick up
+-- this profile, for the user that difference doesn't exist, the
+-- profile is effectively pending. This is consistent with all our
+-- aggregation functions.
+COALESCE(status, '%s') AS status,
+COALESCE(operation_type, '') AS operation_type,
+COALESCE(detail, '') AS detail
+FROM
+host_mdm_apple_declarations
+WHERE
host_uuid = ? AND NOT (operation_type = '%s' AND COALESCE(status, '%s') IN('%s', '%s'))`,
fleet.MDMDeliveryPending,
fleet.MDMOperationTypeRemove,
fleet.MDMDeliveryPending,
fleet.MDMDeliveryVerifying,
fleet.MDMDeliveryVerified,
+ fleet.MDMDeliveryPending,
+ fleet.MDMOperationTypeRemove,
+ fleet.MDMDeliveryPending,
+ fleet.MDMDeliveryVerifying,
+ fleet.MDMDeliveryVerified,
)
var profiles []fleet.HostMDMAppleProfile
- if err := sqlx.SelectContext(ctx, ds.reader(ctx), &profiles, stmt, hostUUID); err != nil {
+ if err := sqlx.SelectContext(ctx, ds.reader(ctx), &profiles, stmt, hostUUID, hostUUID); err != nil {
return nil, err
}
return profiles, nil
@@ -1411,6 +1512,8 @@ func (ds *Datastore) bulkSetPendingMDMAppleHostProfilesDB(
return nil
}
+ appleMDMProfilesDesiredStateQuery := generateDesiredStateQuery("profile")
+
// TODO(mna): the conditions here (and in toRemoveStmt) are subtly different
// than the ones in ListMDMAppleProfilesToInstall/Remove, so I'm keeping
// those statements distinct to avoid introducing a subtle bug, but we should
@@ -1641,20 +1744,30 @@ func (ds *Datastore) bulkSetPendingMDMAppleHostProfilesDB(
return nil
}
-const appleMDMProfilesDesiredStateQuery = `
- -- non label-based profiles
+// mdmEntityTypeToTable tracks what table should be used in the templates for
+// SQL statements based on the given entity type.
+var mdmEntityTypeToTable = map[string]string{
+ "declaration": "declaration",
+ "profile": "configuration_profile",
+}
+
+// generateDesiredStateQuery generates a query string that represents the
+// desired state of an Apple entity based on its type (profile or declaration)
+func generateDesiredStateQuery(entityType string) string {
+ return fmt.Sprintf(`
+ -- non label-based entities
SELECT
- macp.profile_uuid,
+ mae.%[1]s_uuid,
h.uuid as host_uuid,
- macp.identifier as profile_identifier,
- macp.name as profile_name,
- macp.checksum as checksum,
- 0 as count_profile_labels,
+ mae.identifier as %[1]s_identifier,
+ mae.name as %[1]s_name,
+ mae.checksum as checksum,
+ 0 as count_%[1]s_labels,
0 as count_host_labels
FROM
- mdm_apple_configuration_profiles macp
+ mdm_apple_%[2]ss mae
JOIN hosts h
- ON h.team_id = macp.team_id OR (h.team_id IS NULL AND macp.team_id = 0)
+ ON h.team_id = mae.team_id OR (h.team_id IS NULL AND mae.team_id = 0)
JOIN nano_enrollments ne
ON ne.device_id = h.uuid
WHERE
@@ -1663,81 +1776,146 @@ const appleMDMProfilesDesiredStateQuery = `
ne.type = 'Device' AND
NOT EXISTS (
SELECT 1
- FROM mdm_configuration_profile_labels mcpl
- WHERE mcpl.apple_profile_uuid = macp.profile_uuid
+ FROM mdm_%[2]s_labels mel
+ WHERE mel.apple_%[1]s_uuid = mae.%[1]s_uuid
) AND
- ( %s )
+ ( %[3]s )
UNION
- -- label-based profiles where the host is a member of all the labels
+ -- label-based entities where the host is a member of all the labels
SELECT
- macp.profile_uuid,
+ mae.%[1]s_uuid,
h.uuid as host_uuid,
- macp.identifier as profile_identifier,
- macp.name as profile_name,
- macp.checksum as checksum,
- COUNT(*) as count_profile_labels,
+ mae.identifier as %[1]s_identifier,
+ mae.name as %[1]s_name,
+ mae.checksum as checksum,
+ COUNT(*) as count_%[1]s_labels,
COUNT(lm.label_id) as count_host_labels
FROM
- mdm_apple_configuration_profiles macp
+ mdm_apple_%[2]ss mae
JOIN hosts h
- ON h.team_id = macp.team_id OR (h.team_id IS NULL AND macp.team_id = 0)
+ ON h.team_id = mae.team_id OR (h.team_id IS NULL AND mae.team_id = 0)
JOIN nano_enrollments ne
ON ne.device_id = h.uuid
- JOIN mdm_configuration_profile_labels mcpl
- ON mcpl.apple_profile_uuid = macp.profile_uuid
+ JOIN mdm_%[2]s_labels mel
+ ON mel.apple_%[1]s_uuid = mae.%[1]s_uuid
LEFT OUTER JOIN label_membership lm
- ON lm.label_id = mcpl.label_id AND lm.host_id = h.id
+ ON lm.label_id = mel.label_id AND lm.host_id = h.id
WHERE
h.platform = 'darwin' AND
ne.enabled = 1 AND
ne.type = 'Device' AND
- ( %s )
+ ( %[3]s )
GROUP BY
- macp.profile_uuid, h.uuid, macp.identifier, macp.name, macp.checksum
+ mae.%[1]s_uuid, h.uuid, mae.identifier, mae.name, mae.checksum
HAVING
- count_profile_labels > 0 AND count_host_labels = count_profile_labels
-`
+ count_%[1]s_labels > 0 AND count_host_labels = count_%[1]s_labels
+
+ `, entityType, mdmEntityTypeToTable[entityType], "%s")
+}
+
+// generateEntitiesToInstallQuery is a set difference between:
+//
+// - Set A (ds), the "desired state", can be obtained from a JOIN between
+// mdm_apple_x and hosts.
+//
+// - Set B, the "current state" given by host_mdm_apple_x.
+//
+// A - B gives us the entities that need to be installed:
+//
+// - entities that are in A but not in B
+//
+// - entities which contents have changed, but their identifier are
+// the same (by checking the checksums)
+//
+// - entities that are in A and in B, but with an operation type of
+// "remove", regardless of the status. (technically, if status is NULL then
+// the entity should be already installed - it has not been queued for
+// remove yet -, and same if status is failed, but the proper thing to do
+// with it would be to remove the row, not return it as "to install". For
+// simplicity of implementation here (and to err on the safer side - the
+// entity's content could've changed), we'll return it as "to install" for
+// now, which will cause the row to be updated with the correct operation
+// type and status).
+//
+// - entities that are in A and in B, with an operation type of "install"
+// and a NULL status. Other statuses mean that the operation is already in
+// flight (pending), the operation has been completed but is still subject
+// to independent verification by Fleet (verifying), or has reached a terminal
+// state (failed or verified). If the entity's content is edited, all
+// relevant hosts will be marked as status NULL so that it gets
+// re-installed.
+//
+// Note that for label-based entities, only fully-satisfied entities are
+// considered for installation. This means that a broken label-based entity,
+// where one of the labels does not exist anymore, will not be considered for
+// installation.
+func generateEntitiesToInstallQuery(entityType string) string {
+ return fmt.Sprintf(`
+ ( %[3]s ) as ds
+ LEFT JOIN host_mdm_apple_%[1]ss hmae
+ ON hmae.%[1]s_uuid = ds.%[1]s_uuid AND hmae.host_uuid = ds.host_uuid
+ WHERE
+ -- entity has been updated
+ ( hmae.checksum != ds.checksum ) OR
+ -- entity in A but not in B
+ ( hmae.%[1]s_uuid IS NULL AND hmae.host_uuid IS NULL ) OR
+ -- entities in A and B but with operation type "remove"
+ ( hmae.host_uuid IS NOT NULL AND ( hmae.operation_type = ? OR hmae.operation_type IS NULL ) ) OR
+ -- entities in A and B with operation type "install" and NULL status
+ ( hmae.host_uuid IS NOT NULL AND hmae.operation_type = ? AND hmae.status IS NULL )
+`, entityType, mdmEntityTypeToTable[entityType], fmt.Sprintf(generateDesiredStateQuery(entityType), "TRUE", "TRUE"))
+}
+
+// generateEntitiesToRemoveQuery is a set difference between:
+//
+// - Set A (ds), the "desired state", can be obtained from a JOIN between
+// mdm_apple_configuration_x and hosts.
+//
+// - Set B, the "current state" given by host_mdm_apple_x.
+//
+// B - A gives us the entities that need to be removed:
+//
+// - entities that are in B but not in A, except those with operation type
+// "remove" and a terminal state (failed) or a state indicating
+// that the operation is in flight (pending) or the operation has been completed
+// but is still subject to independent verification by Fleet (verifying)
+// or the operation has been completed and independenly verified by Fleet (verified).
+//
+// Any other case are entities that are in both B and A, and as such are
+// processed by the generateEntitiesToInstallQuery query (since they are in
+// both, their desired state is necessarily to be installed).
+//
+// Note that for label-based entities, only those that are fully-sastisfied
+// by the host are considered for install (are part of the desired state used
+// to compute the ones to remove). However, as a special case, a broken
+// label-based entity will NOT be removed from a host where it was
+// previously installed. However, if a host used to satisfy a label-based
+// entity but no longer does (and that label-based entity is not "broken"),
+// the entity will be removed from the host.
+func generateEntitiesToRemoveQuery(entityType string) string {
+ return fmt.Sprintf(`
+ ( %[3]s ) as ds
+ RIGHT JOIN host_mdm_apple_%[1]ss hmae
+ ON hmae.%[1]s_uuid = ds.%[1]s_uuid AND hmae.host_uuid = ds.host_uuid
+ WHERE
+ -- entities that are in B but not in A
+ ds.%[1]s_uuid IS NULL AND ds.host_uuid IS NULL AND
+ -- except "remove" operations in a terminal state or already pending
+ ( hmae.operation_type IS NULL OR hmae.operation_type != ? OR hmae.status IS NULL ) AND
+ -- except "would be removed" entities if they are a broken label-based entities
+ NOT EXISTS (
+ SELECT 1
+ FROM mdm_%[2]s_labels mcpl
+ WHERE
+ mcpl.apple_%[1]s_uuid = hmae.%[1]s_uuid AND
+ mcpl.label_id IS NULL
+ )
+`, entityType, mdmEntityTypeToTable[entityType], fmt.Sprintf(generateDesiredStateQuery(entityType), "TRUE", "TRUE"))
+}
func (ds *Datastore) ListMDMAppleProfilesToInstall(ctx context.Context) ([]*fleet.MDMAppleProfilePayload, error) {
- // The query below is a set difference between:
- //
- // - Set A (ds), the "desired state", can be obtained from a JOIN between
- // mdm_apple_configuration_profiles and hosts.
- //
- // - Set B, the "current state" given by host_mdm_apple_profiles.
- //
- // A - B gives us the profiles that need to be installed:
- //
- // - profiles that are in A but not in B
- //
- // - profiles which contents have changed, but their identifier are
- // the same (by checking the checksums)
- //
- // - profiles that are in A and in B, but with an operation type of
- // "remove", regardless of the status. (technically, if status is NULL then
- // the profile should be already installed - it has not been queued for
- // remove yet -, and same if status is failed, but the proper thing to do
- // with it would be to remove the row, not return it as "to install". For
- // simplicity of implementation here (and to err on the safer side - the
- // profile's content could've changed), we'll return it as "to install" for
- // now, which will cause the row to be updated with the correct operation
- // type and status).
- //
- // - profiles that are in A and in B, with an operation type of "install"
- // and a NULL status. Other statuses mean that the operation is already in
- // flight (pending), the operation has been completed but is still subject
- // to independent verification by Fleet (verifying), or has reached a terminal
- // state (failed or verified). If the profile's content is edited, all
- // relevant hosts will be marked as status NULL so that it gets
- // re-installed.
- //
- // Note that for label-based profiles, only fully-satisfied profiles are
- // considered for installation. This means that a broken label-based profile,
- // where one of the labels does not exist anymore, will not be considered for
- // installation.
-
query := fmt.Sprintf(`
SELECT
ds.profile_uuid,
@@ -1745,82 +1923,26 @@ func (ds *Datastore) ListMDMAppleProfilesToInstall(ctx context.Context) ([]*flee
ds.profile_identifier,
ds.profile_name,
ds.checksum
- FROM ( %s ) as ds
- LEFT JOIN host_mdm_apple_profiles hmap
- ON hmap.profile_uuid = ds.profile_uuid AND hmap.host_uuid = ds.host_uuid
- WHERE
- -- profile has been updated
- ( hmap.checksum != ds.checksum ) OR
- -- profiles in A but not in B
- ( hmap.profile_uuid IS NULL AND hmap.host_uuid IS NULL ) OR
- -- profiles in A and B but with operation type "remove"
- ( hmap.host_uuid IS NOT NULL AND ( hmap.operation_type = ? OR hmap.operation_type IS NULL ) ) OR
- -- profiles in A and B with operation type "install" and NULL status
- ( hmap.host_uuid IS NOT NULL AND hmap.operation_type = ? AND hmap.status IS NULL )
-`, fmt.Sprintf(appleMDMProfilesDesiredStateQuery, "TRUE", "TRUE"))
-
+ FROM %s `,
+ generateEntitiesToInstallQuery("profile"))
var profiles []*fleet.MDMAppleProfilePayload
err := sqlx.SelectContext(ctx, ds.reader(ctx), &profiles, query, fleet.MDMOperationTypeRemove, fleet.MDMOperationTypeInstall)
return profiles, err
}
func (ds *Datastore) ListMDMAppleProfilesToRemove(ctx context.Context) ([]*fleet.MDMAppleProfilePayload, error) {
- // The query below is a set difference between:
- //
- // - Set A (ds), the "desired state", can be obtained from a JOIN between
- // mdm_apple_configuration_profiles and hosts.
- //
- // - Set B, the "current state" given by host_mdm_apple_profiles.
- //
- // B - A gives us the profiles that need to be removed:
- //
- // - profiles that are in B but not in A, except those with operation type
- // "remove" and a terminal state (failed) or a state indicating
- // that the operation is in flight (pending) or the operation has been completed
- // but is still subject to independent verification by Fleet (verifying)
- // or the operation has been completed and independenly verified by Fleet (verified).
- //
- // Any other case are profiles that are in both B and A, and as such are
- // processed by the ListMDMAppleProfilesToInstall method (since they are in
- // both, their desired state is necessarily to be installed).
- //
- // Note that for label-based profiles, only those that are fully-sastisfied
- // by the host are considered for install (are part of the desired state used
- // to compute the ones to remove). However, as a special case, a broken
- // label-based profile will NOT be removed from a host where it was
- // previously installed. However, if a host used to satisfy a label-based
- // profile but no longer does (and that label-based profile is not "broken"),
- // the profile will be removed from the host.
-
query := fmt.Sprintf(`
SELECT
- hmap.profile_uuid,
- hmap.profile_identifier,
- hmap.profile_name,
- hmap.host_uuid,
- hmap.checksum,
- hmap.operation_type,
- COALESCE(hmap.detail, '') as detail,
- hmap.status,
- hmap.command_uuid
- FROM ( %s ) as ds
- RIGHT JOIN host_mdm_apple_profiles hmap
- ON hmap.profile_uuid = ds.profile_uuid AND hmap.host_uuid = ds.host_uuid
- WHERE
- -- profiles that are in B but not in A
- ds.profile_uuid IS NULL AND ds.host_uuid IS NULL AND
- -- except "remove" operations in a terminal state or already pending
- ( hmap.operation_type IS NULL OR hmap.operation_type != ? OR hmap.status IS NULL ) AND
- -- except "would be removed" profiles if they are a broken label-based profile
- NOT EXISTS (
- SELECT 1
- FROM mdm_configuration_profile_labels mcpl
- WHERE
- mcpl.apple_profile_uuid = hmap.profile_uuid AND
- mcpl.label_id IS NULL
- )
-`, fmt.Sprintf(appleMDMProfilesDesiredStateQuery, "TRUE", "TRUE"))
-
+ hmae.profile_uuid,
+ hmae.profile_identifier,
+ hmae.profile_name,
+ hmae.host_uuid,
+ hmae.checksum,
+ hmae.operation_type,
+ COALESCE(hmae.detail, '') as detail,
+ hmae.status,
+ hmae.command_uuid
+ FROM %s`, generateEntitiesToRemoveQuery("profile"))
var profiles []*fleet.MDMAppleProfilePayload
err := sqlx.SelectContext(ctx, ds.reader(ctx), &profiles, query, fleet.MDMOperationTypeRemove)
return profiles, err
@@ -1890,11 +2012,11 @@ func (ds *Datastore) BulkUpsertMDMAppleHostProfiles(ctx context.Context, payload
return err
}
- var (
- args []any
- sb strings.Builder
- batchCount int
- )
+ generateValueArgs := func(p *fleet.MDMAppleBulkUpsertHostProfilePayload) (string, []any) {
+ valuePart := "(?, ?, ?, ?, ?, ?, ?, ?, ?),"
+ args := []any{p.ProfileUUID, p.ProfileIdentifier, p.ProfileName, p.HostUUID, p.Status, p.OperationType, p.Detail, p.CommandUUID, p.Checksum}
+ return valuePart, args
+ }
const defaultBatchSize = 1000 // results in this times 9 placeholders
batchSize := defaultBatchSize
@@ -1902,30 +2024,10 @@ func (ds *Datastore) BulkUpsertMDMAppleHostProfiles(ctx context.Context, payload
batchSize = ds.testUpsertMDMDesiredProfilesBatchSize
}
- resetBatch := func() {
- batchCount = 0
- args = args[:0]
- sb.Reset()
+ if err := batchProcessDB(payload, batchSize, generateValueArgs, executeUpsertBatch); err != nil {
+ return err
}
- for _, p := range payload {
- args = append(args, p.ProfileUUID, p.ProfileIdentifier, p.ProfileName, p.HostUUID, p.Status, p.OperationType, p.Detail, p.CommandUUID, p.Checksum)
- sb.WriteString("(?, ?, ?, ?, ?, ?, ?, ?, ?),")
- batchCount++
-
- if batchCount >= batchSize {
- if err := executeUpsertBatch(sb.String(), args); err != nil {
- return err
- }
- resetBatch()
- }
- }
-
- if batchCount > 0 {
- if err := executeUpsertBatch(sb.String(), args); err != nil {
- return err
- }
- }
return nil
}
@@ -2090,57 +2192,251 @@ func subqueryAppleProfileStatus(status fleet.MDMDeliveryStatus) (string, []any,
return query, args, nil
}
+// subqueryAppleDeclarationStatus builds out the subquery for declaration status
+func subqueryAppleDeclarationStatus() (string, []any, error) {
+ const declNamedStmt = `
+ CASE WHEN EXISTS (
+ SELECT
+ 1
+ FROM
+ host_mdm_apple_declarations d1
+ WHERE
+ h.uuid = d1.host_uuid
+ AND d1.status = :failed) THEN
+ 'declarations_failed'
+ WHEN EXISTS (
+ SELECT
+ 1
+ FROM
+ host_mdm_apple_declarations d2
+ WHERE
+ h.uuid = d2.host_uuid
+ AND(d2.status IS NULL
+ OR d2.status = :pending)
+ AND NOT EXISTS (
+ SELECT
+ 1
+ FROM
+ host_mdm_apple_declarations d3
+ WHERE
+ h.uuid = d3.host_uuid
+ AND d3.status = :failed)) THEN
+ 'declarations_pending'
+ WHEN EXISTS (
+ SELECT
+ 1
+ FROM
+ host_mdm_apple_declarations d4
+ WHERE
+ h.uuid = d4.host_uuid
+ AND d4.status = :verifying
+ AND NOT EXISTS (
+ SELECT
+ 1
+ FROM
+ host_mdm_apple_declarations d5
+ WHERE (h.uuid = d5.host_uuid
+ AND(d5.status IS NULL
+ OR d5.status IN(:pending, :failed))))) THEN
+ 'declarations_verifying'
+ WHEN EXISTS (
+ SELECT
+ 1
+ FROM
+ host_mdm_apple_declarations d6
+ WHERE
+ h.uuid = d6.host_uuid
+ AND d6.status = :verified
+ AND NOT EXISTS (
+ SELECT
+ 1
+ FROM
+ host_mdm_apple_declarations d7
+ WHERE (h.uuid = d7.host_uuid
+ AND(d7.status IS NULL
+ OR d7.status IN(:pending, :failed, :verifying))))) THEN
+ 'declarations_verified'
+ ELSE
+ ''
+ END`
+
+ // TODO: do we need to differentiate between install and remove?
+ arg := map[string]any{
+ // "install": fleet.MDMOperationTypeInstall,
+ // "remove": fleet.MDMOperationTypeRemove,
+ "verifying": fleet.MDMDeliveryVerifying,
+ "failed": fleet.MDMDeliveryFailed,
+ "verified": fleet.MDMDeliveryVerified,
+ "pending": fleet.MDMDeliveryPending,
+ }
+ query, args, err := sqlx.Named(declNamedStmt, arg)
+ if err != nil {
+ return "", nil, fmt.Errorf("subqueryAppleDeclarationStatus: %w", err)
+ }
+
+ return query, args, nil
+}
+
+func subqueryOSSettingsStatusMac() (string, []any, error) {
+ var profArgs []any
+ profFailed, profFailedArgs, err := subqueryAppleProfileStatus(fleet.MDMDeliveryFailed)
+ if err != nil {
+ return "", nil, err
+ }
+ profArgs = append(profArgs, profFailedArgs...)
+
+ profPending, profPendingArgs, err := subqueryAppleProfileStatus(fleet.MDMDeliveryPending)
+ if err != nil {
+ return "", nil, err
+ }
+ profArgs = append(profArgs, profPendingArgs...)
+
+ profVerifying, profVerifyingArgs, err := subqueryAppleProfileStatus(fleet.MDMDeliveryVerifying)
+ if err != nil {
+ return "", nil, err
+ }
+ profArgs = append(profArgs, profVerifyingArgs...)
+
+ profVerified, profVerifiedArgs, err := subqueryAppleProfileStatus(fleet.MDMDeliveryVerified)
+ if err != nil {
+ return "", nil, err
+ }
+ profArgs = append(profArgs, profVerifiedArgs...)
+
+ profStmt := fmt.Sprintf(`
+ CASE WHEN EXISTS (%s) THEN
+ 'profiles_failed'
+ WHEN EXISTS (%s) THEN
+ 'profiles_pending'
+ WHEN EXISTS (%s) THEN
+ 'profiles_verifying'
+ WHEN EXISTS (%s) THEN
+ 'profiles_verified'
+ ELSE
+ ''
+ END`,
+ profFailed,
+ profPending,
+ profVerifying,
+ profVerified,
+ )
+
+ declStmt, declArgs, err := subqueryAppleDeclarationStatus()
+ if err != nil {
+ return "", nil, err
+ }
+
+ stmt := fmt.Sprintf(`
+ CASE (%s)
+ WHEN 'profiles_failed' THEN
+ 'failed'
+ WHEN 'profiles_pending' THEN (
+ CASE (%s)
+ WHEN 'declarations_failed' THEN
+ 'failed'
+ ELSE
+ 'pending'
+ END)
+ WHEN 'profiles_verifying' THEN (
+ CASE (%s)
+ WHEN 'declarations_failed' THEN
+ 'failed'
+ WHEN 'declarations_pending' THEN
+ 'pending'
+ ELSE
+ 'verifying'
+ END)
+ WHEN 'profiles_verified' THEN (
+ CASE (%s)
+ WHEN 'declarations_failed' THEN
+ 'failed'
+ WHEN 'declarations_pending' THEN
+ 'pending'
+ WHEN 'declarations_verifying' THEN
+ 'verifying'
+ ELSE
+ 'verified'
+ END)
+ ELSE
+ REPLACE((%s), 'declarations_', '')
+ END`, profStmt, declStmt, declStmt, declStmt, declStmt)
+
+ args := append(profArgs, declArgs...)
+ args = append(args, declArgs...)
+ args = append(args, declArgs...)
+ args = append(args, declArgs...)
+
+ // FIXME(roberto): we found issues in MySQL 5.7.17 (only that version,
+ // which we must support for now) with prepared statements on this
+ // query. The results returned by the DB were always different what
+ // expected unless the arguments are inlined in the query.
+ //
+ // We decided to do this given:
+ //
+ // - The time constraints we were given to develop DDM
+ // - The fact that all the variables in this query are really strings managed by us
+ // - The imminent deprecation of MySQL 5.7
+ return fmt.Sprintf(strings.Replace(stmt, "?", "'%s'", -1), args...), []any{}, nil
+}
+
func (ds *Datastore) GetMDMAppleProfilesSummary(ctx context.Context, teamID *uint) (*fleet.MDMProfilesSummary, error) {
- var args []interface{}
-
- subqueryFailed, subqueryFailedArgs, err := subqueryAppleProfileStatus(fleet.MDMDeliveryFailed)
+ subquery, args, err := subqueryOSSettingsStatusMac()
if err != nil {
- return nil, ctxerr.Wrap(ctx, err, "building failed subquery")
+ return nil, ctxerr.Wrap(ctx, err, "building os settings subquery")
}
- args = append(args, subqueryFailedArgs...)
-
- subqueryPending, subqueryPendingArgs, err := subqueryAppleProfileStatus(fleet.MDMDeliveryPending)
- if err != nil {
- return nil, ctxerr.Wrap(ctx, err, "building pending subquery")
- }
- args = append(args, subqueryPendingArgs...)
-
- subqueryVerifying, subqueryVerifyingArgs, err := subqueryAppleProfileStatus(fleet.MDMDeliveryVerifying)
- if err != nil {
- return nil, ctxerr.Wrap(ctx, err, "building verifying subquery")
- }
- args = append(args, subqueryVerifyingArgs...)
-
- subqueryVerified, subqueryVerifiedArgs, err := subqueryAppleProfileStatus(fleet.MDMDeliveryVerified)
- if err != nil {
- return nil, ctxerr.Wrap(ctx, err, "building verified subquery")
- }
- args = append(args, subqueryVerifiedArgs...)
sqlFmt := `
- SELECT
- COUNT(CASE WHEN EXISTS (%s) THEN 1 END) AS failed,
- COUNT(CASE WHEN EXISTS (%s) THEN 1 END) AS pending,
- COUNT(CASE WHEN EXISTS (%s) THEN 1 END) AS verifying,
- COUNT(CASE WHEN EXISTS (%s) THEN 1 END) AS verified
- FROM
- hosts h
- WHERE
- h.platform = 'darwin' AND %s`
+SELECT
+ %s as status,
+ COUNT(id) as count
+FROM
+ hosts h
+GROUP BY status, platform, team_id HAVING platform = 'darwin' AND status IN (?, ?, ?, ?) AND %s`
- teamFilter := "h.team_id IS NULL"
+ args = append(args, fleet.MDMDeliveryFailed, fleet.MDMDeliveryPending, fleet.MDMDeliveryVerifying, fleet.MDMDeliveryVerified)
+
+ teamFilter := "team_id IS NULL"
if teamID != nil && *teamID > 0 {
- teamFilter = "h.team_id = ?"
+ teamFilter = "team_id = ?"
args = append(args, *teamID)
}
- stmt := fmt.Sprintf(sqlFmt, subqueryFailed, subqueryPending, subqueryVerifying, subqueryVerified, teamFilter)
- var res fleet.MDMProfilesSummary
- err = sqlx.GetContext(ctx, ds.reader(ctx), &res, stmt, args...)
+ stmt := fmt.Sprintf(sqlFmt, subquery, teamFilter)
+
+ var dest []struct {
+ Count uint `db:"count"`
+ Status string `db:"status"`
+ }
+
+ err = sqlx.SelectContext(ctx, ds.reader(ctx), &dest, stmt, args...)
if 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", 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
}
@@ -3080,3 +3376,608 @@ WHERE h.uuid = ?
return nil
}
+
+func (ds *Datastore) batchSetMDMAppleDeclarations(ctx context.Context, tx sqlx.ExtContext, tmID *uint, incomingDeclarations []*fleet.MDMAppleDeclaration) ([]*fleet.MDMAppleDeclaration, error) {
+ const insertStmt = `
+INSERT INTO mdm_apple_declarations (
+ declaration_uuid,
+ identifier,
+ name,
+ raw_json,
+ checksum,
+ uploaded_at,
+ team_id
+)
+VALUES (
+ ?,?,?,?,UNHEX(?),CURRENT_TIMESTAMP(),?
+)
+ON DUPLICATE KEY UPDATE
+ uploaded_at = IF(checksum = VALUES(checksum) AND name = VALUES(name), uploaded_at, CURRENT_TIMESTAMP()),
+ checksum = VALUES(checksum),
+ name = VALUES(name),
+ raw_json = VALUES(raw_json)
+`
+
+ fmtDeleteStmt := `
+DELETE FROM
+ mdm_apple_declarations
+WHERE
+ team_id = ? AND %s
+`
+ andIdentNotInList := "identifier NOT IN (?)" // added to fmtDeleteStmt if needed
+
+ const loadExistingDecls = `
+SELECT
+ identifier,
+ declaration_uuid,
+ raw_json
+FROM
+ mdm_apple_declarations
+WHERE
+ team_id = ? AND
+ identifier IN (?)
+`
+
+ var declTeamID uint
+ if tmID != nil {
+ declTeamID = *tmID
+ }
+
+ // build a list of identifiers for the incoming declarations, will keep the
+ // existing ones if there's a match and no change
+ incomingIdents := make([]string, len(incomingDeclarations))
+ // at the same time, index the incoming declarations keyed by identifier for ease
+ // or processing
+ incomingDecls := make(map[string]*fleet.MDMAppleDeclaration, len(incomingDeclarations))
+ for i, p := range incomingDeclarations {
+ incomingIdents[i] = p.Identifier
+ incomingDecls[p.Identifier] = p
+ }
+
+ var existingDecls []*fleet.MDMAppleDeclaration
+
+ if len(incomingIdents) > 0 {
+ // load existing declarations that match the incoming declarations by identifiers
+ stmt, args, err := sqlx.In(loadExistingDecls, declTeamID, incomingIdents)
+ if err != nil || strings.HasPrefix(ds.testBatchSetMDMAppleProfilesErr, "inselect") { // TODO(JVE): do we need to create similar errors for testing decls?
+ if err == nil {
+ err = errors.New(ds.testBatchSetMDMAppleProfilesErr)
+ }
+ return nil, ctxerr.Wrap(ctx, err, "build query to load existing declarations")
+ }
+ if err := sqlx.SelectContext(ctx, tx, &existingDecls, stmt, args...); err != nil || strings.HasPrefix(ds.testBatchSetMDMAppleProfilesErr, "select") {
+ if err == nil {
+ err = errors.New(ds.testBatchSetMDMAppleProfilesErr)
+ }
+ return nil, ctxerr.Wrap(ctx, err, "load existing declarations")
+ }
+ }
+
+ // figure out if we need to delete any declarations
+ keepIdents := make([]any, 0, len(incomingIdents))
+ for _, p := range existingDecls {
+ if newP := incomingDecls[p.Identifier]; newP != nil {
+ keepIdents = append(keepIdents, p.Identifier)
+ }
+ }
+
+ var delArgs []any
+ var delStmt string
+ if len(keepIdents) == 0 {
+ // delete all declarations for the team
+ delStmt = fmt.Sprintf(fmtDeleteStmt, "TRUE")
+ delArgs = []any{declTeamID}
+ } else {
+ // delete the obsolete declarations (all those that are not in keepIdents)
+ stmt, args, err := sqlx.In(fmt.Sprintf(fmtDeleteStmt, andIdentNotInList), declTeamID, keepIdents)
+ // if err != nil || strings.HasPrefix(ds.testBatchSetMDMAppleProfilesErr, "inselect") { // TODO(JVE): do we need to create similar errors for testing decls?
+ // if err == nil {
+ // err = errors.New(ds.testBatchSetMDMAppleProfilesErr)
+ // }
+ // return nil, ctxerr.Wrap(ctx, err, "build query to load existing declarations")
+ // }
+ if err != nil {
+ return nil, ctxerr.Wrap(ctx, err, "build query to delete obsolete profiles")
+ }
+ delStmt = stmt
+ delArgs = args
+ }
+
+ if _, err := tx.ExecContext(ctx, delStmt, delArgs...); err != nil || strings.HasPrefix(ds.testBatchSetMDMAppleProfilesErr, "delete") {
+ if err == nil {
+ err = errors.New(ds.testBatchSetMDMAppleProfilesErr)
+ }
+ return nil, ctxerr.Wrap(ctx, err, "delete obsolete declarations")
+ }
+
+ for _, d := range incomingDeclarations {
+ checksum := md5ChecksumScriptContent(string(d.RawJSON))
+ declUUID := fleet.MDMAppleDeclarationUUIDPrefix + uuid.NewString()
+ if _, err := tx.ExecContext(ctx, insertStmt,
+ declUUID,
+ d.Identifier,
+ d.Name,
+ d.RawJSON,
+ checksum,
+ declTeamID); err != nil || strings.HasPrefix(ds.testBatchSetMDMAppleProfilesErr, "insert") {
+ if err == nil {
+ err = errors.New(ds.testBatchSetMDMAppleProfilesErr)
+ }
+ return nil, ctxerr.Wrapf(ctx, err, "insert new/edited declaration with identifier %q", d.Identifier)
+ }
+ }
+
+ incomingLabels := []fleet.ConfigurationProfileLabel{}
+ if len(incomingIdents) > 0 {
+ var newlyInsertedDecls []*fleet.MDMAppleDeclaration
+ // load current declarations (again) that match the incoming declarations by name to grab their uuids
+ // this is an easy way to grab the identifiers for both the existing declarations and the new ones we generated.
+ //
+ // TODO(roberto): if we're a bit careful, we can harvest this
+ // information without this extra request in the previous DB
+ // calls. Due to time constraints, I'm leaving that
+ // optimization for a later iteration.
+ stmt, args, err := sqlx.In(loadExistingDecls, declTeamID, incomingIdents)
+ if err != nil {
+ return nil, ctxerr.Wrap(ctx, err, "build query to load newly inserted declarations")
+ }
+ if err := sqlx.SelectContext(ctx, tx, &newlyInsertedDecls, stmt, args...); err != nil {
+ return nil, ctxerr.Wrap(ctx, err, "load newly inserted declarations")
+ }
+
+ for _, newlyInsertedDecl := range newlyInsertedDecls {
+ incomingDecl, ok := incomingDecls[newlyInsertedDecl.Identifier]
+ if !ok {
+ return nil, ctxerr.Wrapf(ctx, err, "declaration %q is in the database but was not incoming", newlyInsertedDecl.Identifier)
+ }
+
+ for _, label := range incomingDecl.Labels {
+ label.ProfileUUID = newlyInsertedDecl.DeclarationUUID
+ incomingLabels = append(incomingLabels, label)
+ }
+ }
+ }
+
+ if err := batchSetDeclarationLabelAssociationsDB(ctx, tx, incomingLabels); err != nil || strings.HasPrefix(ds.testBatchSetMDMAppleProfilesErr, "labels") {
+ if err == nil {
+ err = errors.New(ds.testBatchSetMDMAppleProfilesErr)
+ }
+ return nil, ctxerr.Wrap(ctx, err, "inserting apple declaration label associations")
+ }
+
+ return incomingDeclarations, nil
+}
+
+func (ds *Datastore) NewMDMAppleDeclaration(ctx context.Context, declaration *fleet.MDMAppleDeclaration) (*fleet.MDMAppleDeclaration, error) {
+ declUUID := fleet.MDMAppleDeclarationUUIDPrefix + uuid.NewString()
+ checksum := md5ChecksumScriptContent(string(declaration.RawJSON))
+
+ stmt := `
+INSERT INTO mdm_apple_declarations (
+ declaration_uuid,
+ team_id,
+ identifier,
+ name,
+ raw_json,
+ checksum,
+ uploaded_at)
+(SELECT ?,?,?,?,?,UNHEX(?),CURRENT_TIMESTAMP() FROM DUAL WHERE
+ NOT EXISTS (
+ SELECT 1 FROM mdm_windows_configuration_profiles WHERE name = ? AND team_id = ?
+ ) AND NOT EXISTS (
+ SELECT 1 FROM mdm_apple_configuration_profiles WHERE name = ? AND team_id = ?
+ )
+)`
+
+ var tmID uint
+ if declaration.TeamID != nil {
+ tmID = *declaration.TeamID
+ }
+
+ err := ds.withTx(ctx, func(tx sqlx.ExtContext) error {
+ res, err := tx.ExecContext(ctx, stmt,
+ declUUID, tmID, declaration.Identifier, declaration.Name, declaration.RawJSON, checksum, declaration.Name, tmID, declaration.Name, tmID)
+ if err != nil {
+ switch {
+ case isDuplicate(err):
+ return ctxerr.Wrap(ctx, formatErrorDuplicateDeclaration(err, declaration))
+ default:
+ return ctxerr.Wrap(ctx, err, "creating new apple mdm declaration")
+ }
+ }
+
+ aff, _ := res.RowsAffected()
+ if aff == 0 {
+ return &existsError{
+ ResourceType: "MDMAppleDeclaration.Name",
+ Identifier: declaration.Name,
+ TeamID: declaration.TeamID,
+ }
+ }
+
+ for i := range declaration.Labels {
+ declaration.Labels[i].ProfileUUID = declUUID
+ }
+ if err := batchSetDeclarationLabelAssociationsDB(ctx, tx, declaration.Labels); err != nil {
+ return ctxerr.Wrap(ctx, err, "inserting mdm declaration label associations")
+ }
+
+ return nil
+ })
+ if err != nil {
+ return nil, ctxerr.Wrap(ctx, err, "inserting declaration and label associations")
+ }
+
+ declaration.DeclarationUUID = declUUID
+ return declaration, nil
+}
+
+func batchSetDeclarationLabelAssociationsDB(ctx context.Context, tx sqlx.ExtContext, declarationLabels []fleet.ConfigurationProfileLabel) error {
+ if len(declarationLabels) == 0 {
+ return nil
+ }
+
+ // delete any profile+label tuple that is NOT in the list of provided tuples
+ // but are associated with the provided profiles (so we don't delete
+ // unrelated profile+label tuples)
+ deleteStmt := `
+ DELETE FROM mdm_declaration_labels
+ WHERE (apple_declaration_uuid, label_id) NOT IN (%s) AND
+ apple_declaration_uuid IN (?)
+ `
+
+ upsertStmt := `
+ INSERT INTO mdm_declaration_labels
+ (apple_declaration_uuid, label_id, label_name)
+ VALUES
+ %s
+ ON DUPLICATE KEY UPDATE
+ label_id = VALUES(label_id)
+ `
+
+ var (
+ insertBuilder strings.Builder
+ deleteBuilder strings.Builder
+ insertParams []any
+ deleteParams []any
+
+ setProfileUUIDs = make(map[string]struct{})
+ )
+ for i, pl := range declarationLabels {
+ if i > 0 {
+ insertBuilder.WriteString(",")
+ deleteBuilder.WriteString(",")
+ }
+ insertBuilder.WriteString("(?, ?, ?)")
+ deleteBuilder.WriteString("(?, ?)")
+ insertParams = append(insertParams, pl.ProfileUUID, pl.LabelID, pl.LabelName)
+ deleteParams = append(deleteParams, pl.ProfileUUID, pl.LabelID)
+
+ setProfileUUIDs[pl.ProfileUUID] = struct{}{}
+ }
+
+ _, err := tx.ExecContext(ctx, fmt.Sprintf(upsertStmt, insertBuilder.String()), insertParams...)
+ if err != nil {
+ if isChildForeignKeyError(err) {
+ // one of the provided labels doesn't exist
+ return foreignKey("mdm_declaration_labels", fmt.Sprintf("(declaration, label)=(%v)", insertParams))
+ }
+
+ return ctxerr.Wrap(ctx, err, "setting label associations for declarations")
+ }
+
+ deleteStmt = fmt.Sprintf(deleteStmt, deleteBuilder.String())
+
+ profUUIDs := make([]string, 0, len(setProfileUUIDs))
+ for k := range setProfileUUIDs {
+ profUUIDs = append(profUUIDs, k)
+ }
+ deleteArgs := append(deleteParams, profUUIDs)
+
+ deleteStmt, args, err := sqlx.In(deleteStmt, deleteArgs...)
+ if err != nil {
+ return ctxerr.Wrap(ctx, err, "sqlx.In delete labels for declarations")
+ }
+ if _, err := tx.ExecContext(ctx, deleteStmt, args...); err != nil {
+ return ctxerr.Wrap(ctx, err, "deleting labels for declarations")
+ }
+
+ return nil
+}
+
+func (ds *Datastore) MDMAppleDDMDeclarationsToken(ctx context.Context, hostUUID string) (*fleet.MDMAppleDDMDeclarationsToken, error) {
+ const stmt = `
+SELECT
+ md5((count(0) + group_concat(hex(mad.checksum)
+ ORDER BY
+ mad.uploaded_at DESC separator ''))) AS checksum,
+ max(mad.created_at) AS latest_created_timestamp
+FROM
+ host_mdm_apple_declarations hmad
+ JOIN mdm_apple_declarations mad ON hmad.declaration_uuid = mad.declaration_uuid
+WHERE
+ hmad.host_uuid = ?`
+
+ var res fleet.MDMAppleDDMDeclarationsToken
+ if err := sqlx.GetContext(ctx, ds.reader(ctx), &res, stmt, hostUUID); err != nil {
+ return nil, ctxerr.Wrap(ctx, err, "get DDM declarations token")
+ }
+
+ return &res, nil
+}
+
+func (ds *Datastore) MDMAppleDDMDeclarationItems(ctx context.Context, hostUUID string) ([]fleet.MDMAppleDDMDeclarationItem, error) {
+ const stmt = `
+SELECT
+ HEX(mad.checksum) as checksum,
+ mad.identifier
+FROM
+ host_mdm_apple_declarations hmad
+ JOIN mdm_apple_declarations mad ON mad.declaration_uuid = hmad.declaration_uuid
+WHERE
+ hmad.host_uuid = ? AND operation_type = ?`
+
+ var res []fleet.MDMAppleDDMDeclarationItem
+ if err := sqlx.SelectContext(ctx, ds.reader(ctx), &res, stmt, hostUUID, fleet.MDMOperationTypeInstall); err != nil {
+ return nil, ctxerr.Wrap(ctx, err, "get DDM declaration items")
+ }
+
+ return res, nil
+}
+
+func (ds *Datastore) MDMAppleDDMDeclarationsResponse(ctx context.Context, identifier string, hostUUID string) (*fleet.MDMAppleDeclaration, error) {
+ // TODO: When hosts table is indexed by uuid, consider joining on hosts to ensure that the
+ // declaration for the host's current team is returned. In the case where the specified
+ // identifier is not unique to the team, the cron should ensure that any conflicting
+ // declarations are removed, but the join would provide an extra layer of safety.
+ const stmt = `
+SELECT
+ mad.raw_json, HEX(mad.checksum) as checksum
+FROM
+ host_mdm_apple_declarations hmad
+ JOIN mdm_apple_declarations mad ON hmad.declaration_uuid = mad.declaration_uuid
+WHERE
+ host_uuid = ? AND identifier = ? AND operation_type = ?`
+
+ var res fleet.MDMAppleDeclaration
+ if err := sqlx.GetContext(ctx, ds.reader(ctx), &res, stmt, hostUUID, identifier, fleet.MDMOperationTypeInstall); err != nil {
+ if err == sql.ErrNoRows {
+ return nil, notFound("MDMAppleDeclaration").WithName(identifier)
+ }
+ return nil, ctxerr.Wrap(ctx, err, "get ddm declarations response")
+ }
+
+ return &res, nil
+}
+
+func (ds *Datastore) MDMAppleBatchSetHostDeclarationState(ctx context.Context) ([]string, error) {
+ var uuids []string
+
+ const defaultBatchSize = 1000
+ batchSize := defaultBatchSize
+ if ds.testUpsertMDMDesiredProfilesBatchSize > 0 {
+ batchSize = ds.testUpsertMDMDesiredProfilesBatchSize
+ }
+
+ err := ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
+ var err error
+ uuids, err = mdmAppleBatchSetHostDeclarationStateDB(ctx, tx, batchSize, &fleet.MDMDeliveryPending)
+ return err
+ })
+
+ return uuids, ctxerr.Wrap(ctx, err, "upserting host declaration state")
+}
+
+func mdmAppleBatchSetHostDeclarationStateDB(ctx context.Context, tx sqlx.ExtContext, batchSize int, status *fleet.MDMDeliveryStatus) ([]string, error) {
+ // once all the declarations are in place, compute the desired state
+ // and find which hosts need a DDM sync.
+ changedDeclarations, err := mdmAppleGetHostsWithChangedDeclarationsDB(ctx, tx)
+ if err != nil {
+ return nil, ctxerr.Wrap(ctx, err, "find hosts with changed declarations")
+ }
+
+ if len(changedDeclarations) == 0 {
+ return []string{}, nil
+ }
+
+ // a host might have more than one declaration to sync, we do this to
+ // collect unique host UUIDs in order to send a single command to each
+ // host in the next step
+ uuidMap := map[string]struct{}{}
+ for _, d := range changedDeclarations {
+ uuidMap[d.HostUUID] = struct{}{}
+ }
+ uuids := make([]string, 0, len(uuidMap))
+ for uuid := range uuidMap {
+ uuids = append(uuids, uuid)
+ }
+
+ // mark the host declarations as pending, this serves two purposes:
+ //
+ // - support the APIs/methods that track host status (summaries, filters, etc)
+ //
+ // - support the DDM endpoints, which use data from the
+ // `host_mdm_apple_declarations` table to compute which declarations to
+ // serve
+ if err := mdmAppleBatchSetPendingHostDeclarationsDB(ctx, tx, batchSize, changedDeclarations, status); err != nil {
+ return nil, ctxerr.Wrap(ctx, err, "batch insert mdm apple host declarations")
+ }
+
+ return uuids, nil
+}
+
+// mdmAppleBatchSetPendingHostDeclarationsDB tracks the current status of all
+// the host declarations provided.
+func mdmAppleBatchSetPendingHostDeclarationsDB(
+ ctx context.Context,
+ tx sqlx.ExtContext,
+ batchSize int,
+ changedDeclarations []*fleet.MDMAppleHostDeclaration,
+ status *fleet.MDMDeliveryStatus,
+) error {
+ baseStmt := `
+ INSERT INTO host_mdm_apple_declarations
+ (host_uuid, status, operation_type, checksum, declaration_uuid, declaration_identifier, declaration_name)
+ VALUES
+ %s
+ ON DUPLICATE KEY UPDATE
+ status = VALUES(status),
+ operation_type = VALUES(operation_type),
+ checksum = VALUES(checksum)
+ `
+
+ executeUpsertBatch := func(valuePart string, args []any) error {
+ _, err := tx.ExecContext(
+ ctx,
+ fmt.Sprintf(baseStmt, strings.TrimSuffix(valuePart, ",")),
+ args...,
+ )
+ return err
+ }
+
+ generateValueArgs := func(d *fleet.MDMAppleHostDeclaration) (string, []any) {
+ valuePart := "(?, ?, ?, ?, ?, ?, ?),"
+ args := []any{d.HostUUID, status, d.OperationType, d.Checksum, d.DeclarationUUID, d.Identifier, d.Name}
+ return valuePart, args
+ }
+
+ err := batchProcessDB(changedDeclarations, batchSize, generateValueArgs, executeUpsertBatch)
+ return ctxerr.Wrap(ctx, err, "inserting changed host declaration state")
+}
+
+// mdmAppleGetHostsWithChangedDeclarationsDB returns a
+// MDMAppleHostDeclaration item for each (host x declaration) pair that
+// needs an status change, this includes declarations to install and
+// declarations to be removed. Those can be differentiated by the
+// OperationType field on each struct.
+func mdmAppleGetHostsWithChangedDeclarationsDB(ctx context.Context, tx sqlx.ExtContext) ([]*fleet.MDMAppleHostDeclaration, error) {
+ stmt := fmt.Sprintf(`
+ (
+ SELECT
+ ds.host_uuid,
+ 'install' as operation_type,
+ ds.checksum,
+ ds.declaration_uuid,
+ ds.declaration_identifier,
+ ds.declaration_name
+ FROM
+ %s
+ )
+ UNION ALL
+ (
+ SELECT
+ hmae.host_uuid,
+ 'remove' as operation_type,
+ hmae.checksum,
+ hmae.declaration_uuid,
+ hmae.declaration_identifier,
+ hmae.declaration_name
+ FROM
+ %s
+ )
+ `,
+ generateEntitiesToInstallQuery("declaration"),
+ generateEntitiesToRemoveQuery("declaration"),
+ )
+
+ var decls []*fleet.MDMAppleHostDeclaration
+ if err := sqlx.SelectContext(ctx, tx, &decls, stmt, fleet.MDMOperationTypeRemove, fleet.MDMOperationTypeInstall, fleet.MDMOperationTypeRemove); err != nil {
+ return nil, ctxerr.Wrap(ctx, err, "running sql statement")
+ }
+ return decls, nil
+}
+
+func (ds *Datastore) MDMAppleStoreDDMStatusReport(ctx context.Context, hostUUID string, updates []*fleet.MDMAppleHostDeclaration) error {
+ getHostDeclarationsStmt := `
+ SELECT host_uuid, status, operation_type, HEX(checksum) as checksum, declaration_uuid, declaration_identifier, declaration_name
+ FROM host_mdm_apple_declarations
+ WHERE host_uuid = ?
+ `
+
+ updateHostDeclarationsStmt := `
+INSERT INTO host_mdm_apple_declarations
+ (host_uuid, declaration_uuid, status, operation_type, detail, declaration_name, declaration_identifier, checksum)
+VALUES
+ %s
+ON DUPLICATE KEY UPDATE
+ status = VALUES(status),
+ operation_type = VALUES(operation_type),
+ detail = VALUES(detail)
+ `
+
+ deletePendingRemovesStmt := `
+ DELETE FROM host_mdm_apple_declarations
+ WHERE host_uuid = ? AND operation_type = 'remove' AND (status = 'pending' OR status IS NULL)
+ `
+
+ var current []*fleet.MDMAppleHostDeclaration
+ if err := sqlx.SelectContext(ctx, ds.reader(ctx), ¤t, getHostDeclarationsStmt, hostUUID); err != nil {
+ return ctxerr.Wrap(ctx, err, "getting current host declarations")
+ }
+
+ updatesByChecksum := make(map[string]*fleet.MDMAppleHostDeclaration, len(updates))
+ for _, u := range updates {
+ updatesByChecksum[u.Checksum] = u
+ }
+
+ var args []any
+ var insertVals strings.Builder
+ for _, c := range current {
+ if u, ok := updatesByChecksum[c.Checksum]; ok {
+ insertVals.WriteString("(?, ?, ?, ?, ?, ?, ?, UNHEX(?)),")
+ args = append(args, hostUUID, c.DeclarationUUID, u.Status, u.OperationType, u.Detail, c.Identifier, c.Name, c.Checksum)
+ }
+ }
+
+ err := ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
+ if len(args) != 0 {
+ stmt := fmt.Sprintf(updateHostDeclarationsStmt, strings.TrimSuffix(insertVals.String(), ","))
+ if _, err := tx.ExecContext(ctx, stmt, args...); err != nil {
+ return ctxerr.Wrap(ctx, err, "updating existing declarations")
+ }
+ }
+
+ if _, err := tx.ExecContext(ctx, deletePendingRemovesStmt, hostUUID); err != nil {
+ return ctxerr.Wrap(ctx, err, "deleting pending removals")
+ }
+
+ return nil
+ })
+
+ return ctxerr.Wrap(ctx, err, "updating host declarations")
+}
+
+func (ds *Datastore) MDMAppleSetDeclarationsAsVerifying(ctx context.Context, hostUUID string) error {
+ stmt := `
+ UPDATE host_mdm_apple_declarations
+ SET status = ?
+ WHERE
+ operation_type = ?
+ AND status = ?
+ AND host_uuid = ?
+ `
+
+ _, err := ds.writer(ctx).ExecContext(
+ ctx, stmt, fleet.MDMDeliveryVerifying,
+ fleet.MDMOperationTypeInstall, fleet.MDMDeliveryPending, hostUUID,
+ )
+ return ctxerr.Wrap(ctx, err, "updating host declaration status to verifying")
+}
+
+func (ds *Datastore) InsertMDMAppleDDMRequest(ctx context.Context, hostUUID, messageType, rawJSON string) error {
+ const stmt = `
+INSERT INTO
+ mdm_apple_declarative_requests (
+ enrollment_id,
+ message_type,
+ raw_json
+ )
+VALUES
+ (?, ?, ?)
+`
+ if _, err := ds.writer(ctx).ExecContext(ctx, stmt, hostUUID, messageType, rawJSON); err != nil {
+ return ctxerr.Wrap(ctx, err, "writing apple declarative request to db")
+ }
+
+ return nil
+}
diff --git a/server/datastore/mysql/apple_mdm_test.go b/server/datastore/mysql/apple_mdm_test.go
index ed38fe7b2a..d989cc2719 100644
--- a/server/datastore/mysql/apple_mdm_test.go
+++ b/server/datastore/mysql/apple_mdm_test.go
@@ -992,6 +992,80 @@ func expectAppleProfiles(
return m
}
+func expectAppleDeclarations(
+ t *testing.T,
+ ds *Datastore,
+ tmID *uint,
+ want []*fleet.MDMAppleDeclaration,
+) map[string]string {
+ if tmID == nil {
+ tmID = ptr.Uint(0)
+ }
+
+ var got []*fleet.MDMAppleDeclaration
+ ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
+ ctx := context.Background()
+ return sqlx.SelectContext(ctx, q, &got, `SELECT * FROM mdm_apple_declarations WHERE team_id = ?`, tmID)
+ })
+
+ // create map of expected declarations keyed by identifier
+ wantMap := make(map[string]*fleet.MDMAppleDeclaration, len(want))
+ for _, cp := range want {
+ wantMap[cp.Identifier] = cp
+ }
+
+ JSONRemarshal := func(bytes []byte) ([]byte, error) {
+ var ifce interface{}
+ err := json.Unmarshal(bytes, &ifce)
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(ifce)
+ }
+
+ // compare only the fields we care about, and build the resulting map of
+ // declaration identifier as key to declaration UUID as value
+ m := make(map[string]string)
+ for _, gotD := range got {
+
+ wantD := wantMap[gotD.Identifier]
+
+ m[gotD.Identifier] = gotD.DeclarationUUID
+ if gotD.TeamID != nil && *gotD.TeamID == 0 {
+ gotD.TeamID = nil
+ }
+
+ // DeclarationUUID is non-empty and starts with "d", but otherwise we don't
+ // care about it for test assertions.
+ require.NotEmpty(t, gotD.DeclarationUUID)
+ require.True(t, strings.HasPrefix(gotD.DeclarationUUID, fleet.MDMAppleDeclarationUUIDPrefix))
+ gotD.DeclarationUUID = ""
+ gotD.Checksum = "" // don't care about md5checksum here
+
+ gotD.CreatedAt = time.Time{}
+
+ gotBytes, err := JSONRemarshal(gotD.RawJSON)
+ require.NoError(t, err)
+
+ wantBytes, err := JSONRemarshal(wantD.RawJSON)
+ require.NoError(t, err)
+
+ require.Equal(t, wantBytes, gotBytes)
+
+ // if an expected uploaded_at timestamp is provided for this declaration, keep
+ // its value, otherwise clear it as we don't care about asserting its
+ // value.
+ if wantD == nil || wantD.UploadedAt.IsZero() {
+ gotD.UploadedAt = time.Time{}
+ }
+
+ require.Equal(t, wantD.Name, gotD.Name)
+ require.Equal(t, wantD.Identifier, gotD.Identifier)
+ require.Equal(t, wantD.Labels, gotD.Labels)
+ }
+ return m
+}
+
func testBatchSetMDMAppleProfiles(t *testing.T, ds *Datastore) {
ctx := context.Background()
applyAndExpect := func(newSet []*fleet.MDMAppleConfigProfile, tmID *uint, want []*fleet.MDMAppleConfigProfile) map[string]string {
@@ -1170,6 +1244,30 @@ func configProfileForTest(t *testing.T, name, identifier, uuid string, labels ..
return cp
}
+func declForTest(name, identifier, payloadContent string, labels ...*fleet.Label) *fleet.MDMAppleDeclaration {
+ tmpl := `{
+ "Type": "com.apple.configuration.decl%s",
+ "Identifier": "com.fleet.config%s",
+ "Payload": {
+ "ServiceType": "com.apple.service%s"
+ }
+ }`
+
+ declBytes := []byte(fmt.Sprintf(tmpl, identifier, identifier, payloadContent))
+
+ decl := &fleet.MDMAppleDeclaration{
+ RawJSON: declBytes,
+ Identifier: fmt.Sprintf("com.fleet.config%s", identifier),
+ Name: name,
+ }
+
+ for _, l := range labels {
+ decl.Labels = append(decl.Labels, fleet.ConfigurationProfileLabel{LabelName: l.Name, LabelID: l.ID})
+ }
+
+ return decl
+}
+
func teamConfigProfileForTest(t *testing.T, name, identifier, uuid string, teamID uint) *fleet.MDMAppleConfigProfile {
prof := configProfileBytesForTest(name, identifier, uuid)
cp, err := fleet.NewMDMAppleConfigProfile(configProfileBytesForTest(name, identifier, uuid), &teamID)
@@ -1689,13 +1787,19 @@ func testAggregateMacOSSettingsStatusWithFileVault(t *testing.T, ds *Datastore)
expectedIDs = append(expectedIDs, h.ID)
}
- gotHosts, err := ds.ListHosts(ctx, fleet.TeamFilter{User: &fleet.User{GlobalRole: ptr.String("admin")}}, fleet.HostListOptions{MacOSSettingsFilter: status, TeamFilter: teamID})
+ gotHosts, err := ds.ListHosts(
+ ctx,
+ fleet.TeamFilter{User: &fleet.User{GlobalRole: ptr.String("admin")}},
+ fleet.HostListOptions{MacOSSettingsFilter: status, TeamFilter: teamID},
+ )
gotIDs := []uint{}
for _, h := range gotHosts {
gotIDs = append(gotIDs, h.ID)
}
- return assert.NoError(t, err) && assert.Len(t, gotHosts, len(expected)) && assert.ElementsMatch(t, expectedIDs, gotIDs)
+ return assert.NoError(t, err) &&
+ assert.Len(t, gotHosts, len(expected)) &&
+ assert.ElementsMatch(t, expectedIDs, gotIDs)
}
var hosts []*fleet.Host
@@ -2517,7 +2621,7 @@ func TestMDMAppleFileVaultSummary(t *testing.T) {
allProfilesSummary, err = ds.GetMDMAppleProfilesSummary(ctx, nil)
require.NoError(t, err)
- require.NotNil(t, fvProfileSummary)
+ require.NotNil(t, allProfilesSummary)
require.Equal(t, uint(2), allProfilesSummary.Pending)
require.Equal(t, uint(0), allProfilesSummary.Failed)
require.Equal(t, uint(1), allProfilesSummary.Verifying)
@@ -2543,7 +2647,7 @@ func TestMDMAppleFileVaultSummary(t *testing.T) {
allProfilesSummary, err = ds.GetMDMAppleProfilesSummary(ctx, nil)
require.NoError(t, err)
- require.NotNil(t, fvProfileSummary)
+ require.NotNil(t, allProfilesSummary)
require.Equal(t, uint(2), allProfilesSummary.Pending)
require.Equal(t, uint(0), allProfilesSummary.Failed)
require.Equal(t, uint(1), allProfilesSummary.Verifying)
@@ -2571,7 +2675,7 @@ func TestMDMAppleFileVaultSummary(t *testing.T) {
allProfilesSummary, err = ds.GetMDMAppleProfilesSummary(ctx, nil)
require.NoError(t, err)
- require.NotNil(t, fvProfileSummary)
+ require.NotNil(t, allProfilesSummary)
require.Equal(t, uint(2), allProfilesSummary.Pending)
require.Equal(t, uint(0), allProfilesSummary.Failed)
require.Equal(t, uint(1), allProfilesSummary.Verifying)
@@ -2593,7 +2697,7 @@ func TestMDMAppleFileVaultSummary(t *testing.T) {
allProfilesSummary, err = ds.GetMDMAppleProfilesSummary(ctx, nil)
require.NoError(t, err)
- require.NotNil(t, fvProfileSummary)
+ require.NotNil(t, allProfilesSummary)
require.Equal(t, uint(2), allProfilesSummary.Pending)
require.Equal(t, uint(1), allProfilesSummary.Failed)
require.Equal(t, uint(1), allProfilesSummary.Verifying)
@@ -2615,7 +2719,7 @@ func TestMDMAppleFileVaultSummary(t *testing.T) {
allProfilesSummary, err = ds.GetMDMAppleProfilesSummary(ctx, nil)
require.NoError(t, err)
- require.NotNil(t, fvProfileSummary)
+ require.NotNil(t, allProfilesSummary)
require.Equal(t, uint(3), allProfilesSummary.Pending)
require.Equal(t, uint(1), allProfilesSummary.Failed)
require.Equal(t, uint(1), allProfilesSummary.Verifying)
@@ -2645,7 +2749,7 @@ func TestMDMAppleFileVaultSummary(t *testing.T) {
allProfilesSummary, err = ds.GetMDMAppleProfilesSummary(ctx, &tm.ID)
require.NoError(t, err)
- require.NotNil(t, fvProfileSummary)
+ require.NotNil(t, allProfilesSummary)
require.Equal(t, uint(0), allProfilesSummary.Pending)
require.Equal(t, uint(0), allProfilesSummary.Failed)
require.Equal(t, uint(1), allProfilesSummary.Verifying)
@@ -2671,7 +2775,7 @@ func TestMDMAppleFileVaultSummary(t *testing.T) {
allProfilesSummary, err = ds.GetMDMAppleProfilesSummary(ctx, &tm.ID)
require.NoError(t, err)
- require.NotNil(t, fvProfileSummary)
+ require.NotNil(t, allProfilesSummary)
require.Equal(t, uint(0), allProfilesSummary.Pending)
require.Equal(t, uint(0), allProfilesSummary.Failed)
require.Equal(t, uint(0), allProfilesSummary.Verifying)
diff --git a/server/datastore/mysql/hosts.go b/server/datastore/mysql/hosts.go
index ca8986e2e5..7d94e41c9e 100644
--- a/server/datastore/mysql/hosts.go
+++ b/server/datastore/mysql/hosts.go
@@ -1211,34 +1211,22 @@ func filterHostsByMacOSSettingsStatus(sql string, opt fleet.HostListOptions, par
return sql, params, nil
}
- newSQL := ""
+ whereStatus := ""
+ // macOS settings filter is not compatible with the "all teams" option so append the "no
+ // team" filter here (note that filterHostsByTeam applies the "no team" filter if TeamFilter == 0)
if opt.TeamFilter == nil {
- // macOS settings filter is not compatible with the "all teams" option so append the "no
- // team" filter here (note that filterHostsByTeam applies the "no team" filter if TeamFilter == 0)
- newSQL += ` AND h.team_id IS NULL`
+ whereStatus += ` AND h.team_id IS NULL`
}
- var subquery string
- var subqueryParams []any
- var err error
- switch opt.MacOSSettingsFilter {
- case fleet.OSSettingsFailed:
- subquery, subqueryParams, err = subqueryAppleProfileStatus(fleet.MDMDeliveryFailed)
- case fleet.OSSettingsPending:
- subquery, subqueryParams, err = subqueryAppleProfileStatus(fleet.MDMDeliveryPending)
- case fleet.OSSettingsVerifying:
- subquery, subqueryParams, err = subqueryAppleProfileStatus(fleet.MDMDeliveryVerifying)
- case fleet.OSSettingsVerified:
- subquery, subqueryParams, err = subqueryAppleProfileStatus(fleet.MDMDeliveryVerified)
- }
+ subqueryStatus, paramsStatus, err := subqueryOSSettingsStatusMac()
if err != nil {
- return "", nil, fmt.Errorf("building subquery for %s filter: %w", opt.MacOSSettingsFilter, err)
- }
- if subquery != "" {
- newSQL += fmt.Sprintf(` AND EXISTS (%s)`, subquery)
+ return "", nil, err
}
- return sql + newSQL, append(params, subqueryParams...), nil
+ whereStatus += fmt.Sprintf(` AND %s = ?`, subqueryStatus)
+ paramsStatus = append(paramsStatus, opt.MacOSSettingsFilter)
+
+ return sql + whereStatus, append(params, paramsStatus...), nil
}
func filterHostsByMacOSDiskEncryptionStatus(sql string, opt fleet.HostListOptions, params []interface{}) (string, []interface{}) {
@@ -1286,30 +1274,16 @@ func (ds *Datastore) filterHostsByOSSettingsStatus(sql string, opt fleet.HostLis
sqlFmt += ` AND h.team_id IS NULL`
}
var whereMacOS, whereWindows string
- sqlFmt += ` AND ((h.platform = 'windows' AND (%s)) OR (h.platform = 'darwin' AND (%s)))`
+ sqlFmt += `
+AND ((h.platform = 'windows' AND (%s))
+OR (h.platform = 'darwin' AND (%s)))`
- // construct the WHERE for macOS
- var subqueryMacOS string
- var paramsMacOS []interface{}
- var err error
- switch opt.OSSettingsFilter {
- case fleet.OSSettingsFailed:
- subqueryMacOS, paramsMacOS, err = subqueryAppleProfileStatus(fleet.MDMDeliveryFailed)
- case fleet.OSSettingsPending:
- subqueryMacOS, paramsMacOS, err = subqueryAppleProfileStatus(fleet.MDMDeliveryPending)
- case fleet.OSSettingsVerifying:
- subqueryMacOS, paramsMacOS, err = subqueryAppleProfileStatus(fleet.MDMDeliveryVerifying)
- case fleet.OSSettingsVerified:
- subqueryMacOS, paramsMacOS, err = subqueryAppleProfileStatus(fleet.MDMDeliveryVerified)
- }
+ whereMacOS, paramsMacOS, err := subqueryOSSettingsStatusMac()
if err != nil {
- return "", nil, fmt.Errorf("building subquery for %s filter: %w", opt.OSSettingsFilter, err)
- }
- if subqueryMacOS != "" {
- whereMacOS = "EXISTS (" + subqueryMacOS + ")"
- } else {
- whereMacOS = "FALSE"
+ return "", nil, err
}
+ whereMacOS += ` = ?`
+ paramsMacOS = append(paramsMacOS, opt.OSSettingsFilter)
// construct the WHERE for windows
whereWindows = `hmdm.name = ? AND hmdm.enrolled = 1 AND hmdm.is_server = 0`
diff --git a/server/datastore/mysql/labels_test.go b/server/datastore/mysql/labels_test.go
index eb1ddcd9fa..ea9d455c9a 100644
--- a/server/datastore/mysql/labels_test.go
+++ b/server/datastore/mysql/labels_test.go
@@ -364,6 +364,7 @@ func testLabelsListHostsInLabelAndStatus(t *testing.T, db *Datastore) {
NodeKey: ptr.String("1"),
UUID: "1",
Hostname: "foo.local",
+ Platform: "darwin",
})
require.NoError(t, err)
@@ -377,6 +378,7 @@ func testLabelsListHostsInLabelAndStatus(t *testing.T, db *Datastore) {
NodeKey: ptr.String("2"),
UUID: "2",
Hostname: "bar.local",
+ Platform: "darwin",
})
require.NoError(t, err)
h3, err := db.NewHost(context.Background(), &fleet.Host{
@@ -388,6 +390,7 @@ func testLabelsListHostsInLabelAndStatus(t *testing.T, db *Datastore) {
NodeKey: ptr.String("3"),
UUID: "3",
Hostname: "baz.local",
+ Platform: "darwin",
})
require.NoError(t, err)
@@ -427,6 +430,7 @@ func testLabelsListHostsInLabelAndTeamFilter(deferred bool, t *testing.T, db *Da
NodeKey: ptr.String("1"),
UUID: "1",
Hostname: "foo.local",
+ Platform: "darwin",
})
require.Nil(t, err)
@@ -440,6 +444,7 @@ func testLabelsListHostsInLabelAndTeamFilter(deferred bool, t *testing.T, db *Da
NodeKey: ptr.String("2"),
UUID: "2",
Hostname: "bar.local",
+ Platform: "darwin",
})
require.Nil(t, err)
diff --git a/server/datastore/mysql/mdm.go b/server/datastore/mysql/mdm.go
index 8960bb6287..0ef788c464 100644
--- a/server/datastore/mysql/mdm.go
+++ b/server/datastore/mysql/mdm.go
@@ -82,7 +82,6 @@ func (ds *Datastore) ListMDMCommands(
tmFilter fleet.TeamFilter,
listOpts *fleet.MDMCommandListOptions,
) ([]*fleet.MDMCommand, error) {
-
jointStmt := getCombinedMDMCommandsQuery() + ds.whereFilterHostsByTeams(tmFilter, "h")
jointStmt, params := appendListOptionsWithCursorToSQL(jointStmt, nil, &listOpts.ListOptions)
var results []*fleet.MDMCommand
@@ -102,7 +101,7 @@ func (ds *Datastore) getMDMCommand(ctx context.Context, q sqlx.QueryerContext, c
return &cmd, nil
}
-func (ds *Datastore) BatchSetMDMProfiles(ctx context.Context, tmID *uint, macProfiles []*fleet.MDMAppleConfigProfile, winProfiles []*fleet.MDMWindowsConfigProfile) error {
+func (ds *Datastore) BatchSetMDMProfiles(ctx context.Context, tmID *uint, macProfiles []*fleet.MDMAppleConfigProfile, winProfiles []*fleet.MDMWindowsConfigProfile, macDeclarations []*fleet.MDMAppleDeclaration) error {
return ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
if err := ds.batchSetMDMWindowsProfilesDB(ctx, tx, tmID, winProfiles); err != nil {
return ctxerr.Wrap(ctx, err, "batch set windows profiles")
@@ -112,6 +111,10 @@ func (ds *Datastore) BatchSetMDMProfiles(ctx context.Context, tmID *uint, macPro
return ctxerr.Wrap(ctx, err, "batch set apple profiles")
}
+ if _, err := ds.batchSetMDMAppleDeclarations(ctx, tx, tmID, macDeclarations); err != nil {
+ return ctxerr.Wrap(ctx, err, "batch set apple declarations")
+ }
+
return nil
})
}
@@ -122,6 +125,7 @@ func (ds *Datastore) ListMDMConfigProfiles(ctx context.Context, teamID *uint, op
var profs []*fleet.MDMConfigProfilePayload
+ // TODO(roberto): Consider using UNION ALL here, as we know there won't be any duplicates between the tables.
const selectStmt = `
SELECT
profile_uuid,
@@ -164,6 +168,20 @@ FROM (
WHERE
team_id = ? AND
name NOT IN (?)
+
+ UNION
+
+ SELECT
+ declaration_uuid AS profile_uuid,
+ team_id,
+ name,
+ 'darwin' AS platform,
+ identifier,
+ checksum AS checksum,
+ created_at,
+ uploaded_at
+ FROM mdm_apple_declarations
+ WHERE team_id = ?
) as combined_profiles
`
@@ -183,7 +201,7 @@ FROM (
fleetNames = append(fleetNames, k)
}
- args := []any{globalOrTeamID, fleetIdentifiers, globalOrTeamID, fleetNames}
+ args := []any{globalOrTeamID, fleetIdentifiers, globalOrTeamID, fleetNames, globalOrTeamID}
stmt, args := appendListOptionsWithCursorToSQL(selectStmt, args, &opt)
stmt, args, err := sqlx.In(stmt, args...)
@@ -205,15 +223,20 @@ FROM (
}
// load the labels associated with those profiles
- var winProfUUIDs, macProfUUIDs []string
+ var winProfUUIDs, macProfUUIDs, macDeclUUIDs []string
for _, prof := range profs {
if prof.Platform == "windows" {
winProfUUIDs = append(winProfUUIDs, prof.ProfileUUID)
} else {
+ if strings.HasPrefix(prof.ProfileUUID, fleet.MDMAppleDeclarationUUIDPrefix) {
+ macDeclUUIDs = append(macDeclUUIDs, prof.ProfileUUID)
+ continue
+ }
+
macProfUUIDs = append(macProfUUIDs, prof.ProfileUUID)
}
}
- labels, err := ds.listProfileLabelsForProfiles(ctx, winProfUUIDs, macProfUUIDs)
+ labels, err := ds.listProfileLabelsForProfiles(ctx, winProfUUIDs, macProfUUIDs, macDeclUUIDs)
if err != nil {
return nil, nil, err
}
@@ -232,7 +255,7 @@ FROM (
return profs, metaData, nil
}
-func (ds *Datastore) listProfileLabelsForProfiles(ctx context.Context, winProfUUIDs, macProfUUIDs []string) ([]fleet.ConfigurationProfileLabel, error) {
+func (ds *Datastore) listProfileLabelsForProfiles(ctx context.Context, winProfUUIDs, macProfUUIDs, macDeclUUIDs []string) ([]fleet.ConfigurationProfileLabel, error) {
// load the labels associated with those profiles
const labelsStmt = `
SELECT
@@ -245,6 +268,16 @@ FROM
WHERE
mcpl.apple_profile_uuid IN (?) OR
mcpl.windows_profile_uuid IN (?)
+UNION ALL
+SELECT
+ apple_declaration_uuid as profile_uuid,
+ label_name,
+ COALESCE(label_id, 0) as label_id,
+ IF(label_id IS NULL, 1, 0) as broken
+FROM
+ mdm_declaration_labels mdl
+WHERE
+ mdl.apple_declaration_uuid IN (?)
ORDER BY
profile_uuid, label_name
`
@@ -257,8 +290,11 @@ ORDER BY
if len(macProfUUIDs) == 0 {
macProfUUIDs = []string{"-"}
}
+ if len(macDeclUUIDs) == 0 {
+ macDeclUUIDs = []string{"-"}
+ }
- stmt, args, err := sqlx.In(labelsStmt, macProfUUIDs, winProfUUIDs)
+ stmt, args, err := sqlx.In(labelsStmt, macProfUUIDs, winProfUUIDs, macDeclUUIDs)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "sqlx.In to list labels for profiles")
}
@@ -415,6 +451,20 @@ WHERE
return ctxerr.Wrap(ctx, err, "bulk set pending windows host profiles")
}
+ const defaultBatchSize = 1000
+ batchSize := defaultBatchSize
+ if ds.testUpsertMDMDesiredProfilesBatchSize > 0 {
+ batchSize = ds.testUpsertMDMDesiredProfilesBatchSize
+ }
+ // TODO(roberto): this method currently sets the state of all
+ // declarations for all hosts. I don't see an immediate concern
+ // (and my hunch is that we could even do the same for
+ // profiles) but this could be optimized to use only a provided
+ // set of host uuids.
+ if _, err := mdmAppleBatchSetHostDeclarationStateDB(ctx, tx, batchSize, nil); err != nil {
+ return ctxerr.Wrap(ctx, err, "bulk set pending apple declarations")
+ }
+
return nil
})
}
diff --git a/server/datastore/mysql/mdm_test.go b/server/datastore/mysql/mdm_test.go
index 6cd8d0c783..3f4988d13e 100644
--- a/server/datastore/mysql/mdm_test.go
+++ b/server/datastore/mysql/mdm_test.go
@@ -7,6 +7,7 @@ import (
"fmt"
"sort"
"strconv"
+ "strings"
"testing"
"time"
@@ -43,12 +44,12 @@ func TestMDMShared(t *testing.T) {
{"TestMDMEULA", testMDMEULA},
{"TestGetHostCertAssociationsToExpire", testSCEPRenewalHelpers},
{"TestSCEPRenewalHelpers", testSCEPRenewalHelpers},
+ {"TestMDMProfilesSummaryAndHostFilters", testMDMProfilesSummaryAndHostFilters},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
defer TruncateTables(t, ds)
-
c.fn(t, ds)
})
}
@@ -189,15 +190,18 @@ func testBatchSetMDMProfiles(t *testing.T, ds *Datastore) {
applyAndExpect := func(
newAppleSet []*fleet.MDMAppleConfigProfile,
newWindowsSet []*fleet.MDMWindowsConfigProfile,
+ newAppleDeclSet []*fleet.MDMAppleDeclaration,
tmID *uint,
wantApple []*fleet.MDMAppleConfigProfile,
wantWindows []*fleet.MDMWindowsConfigProfile,
+ wantAppleDecl []*fleet.MDMAppleDeclaration,
) {
ctx := context.Background()
- err := ds.BatchSetMDMProfiles(ctx, tmID, newAppleSet, newWindowsSet)
+ err := ds.BatchSetMDMProfiles(ctx, tmID, newAppleSet, newWindowsSet, newAppleDeclSet)
require.NoError(t, err)
expectAppleProfiles(t, ds, tmID, wantApple)
expectWindowsProfiles(t, ds, tmID, wantWindows)
+ expectAppleDeclarations(t, ds, tmID, wantAppleDecl)
}
withTeamIDApple := func(p *fleet.MDMAppleConfigProfile, tmID uint) *fleet.MDMAppleConfigProfile {
@@ -205,30 +209,39 @@ func testBatchSetMDMProfiles(t *testing.T, ds *Datastore) {
return p
}
+ withTeamIDDecl := func(d *fleet.MDMAppleDeclaration, tmID uint) *fleet.MDMAppleDeclaration {
+ d.TeamID = &tmID
+ return d
+ }
+
withTeamIDWindows := func(p *fleet.MDMWindowsConfigProfile, tmID uint) *fleet.MDMWindowsConfigProfile {
p.TeamID = &tmID
return p
}
// empty set for no team (both Apple and Windows)
- applyAndExpect(nil, nil, nil, nil, nil)
+ applyAndExpect(nil, nil, nil, nil, nil, nil, nil)
// single Apple and Windows profile set for a specific team
applyAndExpect(
[]*fleet.MDMAppleConfigProfile{configProfileForTest(t, "N1", "I1", "a")},
[]*fleet.MDMWindowsConfigProfile{windowsConfigProfileForTest(t, "W1", "l1")},
+ []*fleet.MDMAppleDeclaration{declForTest("D1", "D1", "foo")},
ptr.Uint(1),
[]*fleet.MDMAppleConfigProfile{withTeamIDApple(configProfileForTest(t, "N1", "I1", "a"), 1)},
[]*fleet.MDMWindowsConfigProfile{withTeamIDWindows(windowsConfigProfileForTest(t, "W1", "l1"), 1)},
+ []*fleet.MDMAppleDeclaration{withTeamIDDecl(declForTest("D1", "D1", "foo"), 1)},
)
// single Apple and Windows profile set for no team
applyAndExpect(
[]*fleet.MDMAppleConfigProfile{configProfileForTest(t, "N1", "I1", "a")},
[]*fleet.MDMWindowsConfigProfile{windowsConfigProfileForTest(t, "W1", "l1")},
+ []*fleet.MDMAppleDeclaration{declForTest("D1", "D1", "foo")},
nil,
[]*fleet.MDMAppleConfigProfile{configProfileForTest(t, "N1", "I1", "a")},
[]*fleet.MDMWindowsConfigProfile{windowsConfigProfileForTest(t, "W1", "l1")},
+ []*fleet.MDMAppleDeclaration{declForTest("D1", "D1", "foo")},
)
// new Apple and Windows profile sets for a specific team
@@ -241,6 +254,10 @@ func testBatchSetMDMProfiles(t *testing.T, ds *Datastore) {
windowsConfigProfileForTest(t, "W1", "l1"), // unchanged
windowsConfigProfileForTest(t, "W2", "l2"),
},
+ []*fleet.MDMAppleDeclaration{
+ declForTest("D1", "D1", "foo"), // unchanged
+ declForTest("D2", "D2", "foo"),
+ },
ptr.Uint(1),
[]*fleet.MDMAppleConfigProfile{
withTeamIDApple(configProfileForTest(t, "N1", "I1", "a"), 1),
@@ -250,6 +267,10 @@ func testBatchSetMDMProfiles(t *testing.T, ds *Datastore) {
withTeamIDWindows(windowsConfigProfileForTest(t, "W1", "l1"), 1),
withTeamIDWindows(windowsConfigProfileForTest(t, "W2", "l2"), 1),
},
+ []*fleet.MDMAppleDeclaration{
+ withTeamIDDecl(declForTest("D1", "D1", "foo"), 1),
+ withTeamIDDecl(declForTest("D2", "D2", "foo"), 1),
+ },
)
// edited profiles, unchanged profiles, and new profiles for a specific team
@@ -264,6 +285,11 @@ func testBatchSetMDMProfiles(t *testing.T, ds *Datastore) {
windowsConfigProfileForTest(t, "W2", "l2"), // unchanged
windowsConfigProfileForTest(t, "W3", "l3"), // new
},
+ []*fleet.MDMAppleDeclaration{
+ declForTest("D1", "D1", "foo-updated"), // content updated
+ declForTest("D2", "D2", "foo"), // unchanged
+ declForTest("D3", "D3", "bar"), // new
+ },
ptr.Uint(1),
[]*fleet.MDMAppleConfigProfile{
withTeamIDApple(configProfileForTest(t, "N1", "I1", "a-updated"), 1),
@@ -275,6 +301,11 @@ func testBatchSetMDMProfiles(t *testing.T, ds *Datastore) {
withTeamIDWindows(windowsConfigProfileForTest(t, "W2", "l2"), 1),
withTeamIDWindows(windowsConfigProfileForTest(t, "W3", "l3"), 1),
},
+ []*fleet.MDMAppleDeclaration{
+ withTeamIDDecl(declForTest("D1", "D1", "foo-updated"), 1),
+ withTeamIDDecl(declForTest("D2", "D2", "foo"), 1),
+ withTeamIDDecl(declForTest("D3", "D3", "bar"), 1),
+ },
)
// new Apple and Windows profiles to no team
@@ -287,6 +318,10 @@ func testBatchSetMDMProfiles(t *testing.T, ds *Datastore) {
windowsConfigProfileForTest(t, "W4", "l4"),
windowsConfigProfileForTest(t, "W5", "l5"),
},
+ []*fleet.MDMAppleDeclaration{
+ declForTest("D5", "D4", "foo"),
+ declForTest("D4", "D5", "foo"),
+ },
nil,
[]*fleet.MDMAppleConfigProfile{
configProfileForTest(t, "N4", "I4", "d"),
@@ -296,10 +331,14 @@ func testBatchSetMDMProfiles(t *testing.T, ds *Datastore) {
windowsConfigProfileForTest(t, "W4", "l4"),
windowsConfigProfileForTest(t, "W5", "l5"),
},
+ []*fleet.MDMAppleDeclaration{
+ declForTest("D5", "D4", "foo"),
+ declForTest("D4", "D5", "foo"),
+ },
)
// Test Case 8: Clear profiles for a specific team
- applyAndExpect(nil, nil, ptr.Uint(1), nil, nil)
+ applyAndExpect(nil, nil, nil, ptr.Uint(1), nil, nil, nil)
}
func testListMDMConfigProfiles(t *testing.T, ds *Datastore) {
@@ -612,9 +651,30 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
wantProfs = sortProfs(wantProfs)
for i, wp := range wantProfs {
gp := gotProfs[i]
- require.Equal(t, wp.ProfileUUID, gp.ProfileUUID, "host uuid: %s, prof id or name: %s", h.UUID, gp.IdentifierOrName)
- require.Equal(t, wp.Status, gp.Status, "host uuid: %s, prof id or name: %s", h.UUID, gp.IdentifierOrName)
- require.Equal(t, wp.OperationType, gp.OperationType, "host uuid: %s, prof id or name: %s", h.UUID, gp.IdentifierOrName)
+ require.Equal(
+ t,
+ wp.ProfileUUID,
+ gp.ProfileUUID,
+ "host uuid: %s, prof id or name: %s",
+ h.UUID,
+ gp.IdentifierOrName,
+ )
+ require.Equal(
+ t,
+ wp.Status,
+ gp.Status,
+ "host uuid: %s, prof id or name: %s",
+ h.UUID,
+ gp.IdentifierOrName,
+ )
+ require.Equal(
+ t,
+ wp.OperationType,
+ gp.OperationType,
+ "host uuid: %s, prof id or name: %s",
+ h.UUID,
+ gp.IdentifierOrName,
+ )
}
}
}
@@ -719,18 +779,22 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
configProfileForTest(t, "G2a", "G2a", "b"),
configProfileForTest(t, "G3a", "G3a", "c"),
}
+ macGlobalDeclarations := []*fleet.MDMAppleDeclaration{
+ declForTest("G1d", "G1d", "foo"),
+ declForTest("G2d", "G2d", "bar"),
+ }
winGlobalProfiles := []*fleet.MDMWindowsConfigProfile{
windowsConfigProfileForTest(t, "G1w", "L1"),
windowsConfigProfileForTest(t, "G2w", "L2"),
windowsConfigProfileForTest(t, "G3w", "L3"),
}
- err = ds.BatchSetMDMProfiles(ctx, nil, macGlobalProfiles, winGlobalProfiles)
+ err = ds.BatchSetMDMProfiles(ctx, nil, macGlobalProfiles, winGlobalProfiles, macGlobalDeclarations)
require.NoError(t, err)
macGlobalProfiles, err = ds.ListMDMAppleConfigProfiles(ctx, nil)
require.NoError(t, err)
require.Len(t, macGlobalProfiles, 3)
globalProfiles := getProfs(nil)
- require.Len(t, globalProfiles, 6)
+ require.Len(t, globalProfiles, 8)
// list profiles to install, should result in the global profiles for all
// enrolled hosts
@@ -754,36 +818,153 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
require.NoError(t, err)
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[0].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[1].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[2].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[3].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[4].Identifier,
+ },
},
darwinHosts[1]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[0].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[1].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[2].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[3].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[4].Identifier,
+ },
},
darwinHosts[2]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[0].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[1].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[2].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[3].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[4].Identifier,
+ },
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
- {ProfileUUID: globalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: globalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: globalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[1]: {
- {ProfileUUID: globalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: globalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: globalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[2]: {
- {ProfileUUID: globalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: globalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: globalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
})
@@ -812,37 +993,148 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
require.Len(t, toRemoveWindows, 3)
// update status of the moved host (team has no profiles)
- err = ds.BulkSetPendingMDMHostProfiles(ctx, hostIDsFromHosts(darwinHosts[0], windowsHosts[0]), nil, nil, nil)
+ err = ds.BulkSetPendingMDMHostProfiles(
+ ctx,
+ hostIDsFromHosts(darwinHosts[0], windowsHosts[0]),
+ nil,
+ nil,
+ nil,
+ )
require.NoError(t, err)
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[0].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[1].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[2].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[3].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[4].Identifier,
+ },
},
darwinHosts[1]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[0].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[1].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[2].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[3].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[4].Identifier,
+ },
},
darwinHosts[2]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[0].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[1].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[2].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[3].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[4].Identifier,
+ },
},
unenrolledHost: {},
linuxHost: {},
// windows profiles are directly deleted without a pending state (there's no on-host removal of profiles)
windowsHosts[0]: {},
windowsHosts[1]: {
- {ProfileUUID: globalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: globalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: globalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[2]: {
- {ProfileUUID: globalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: globalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: globalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
})
@@ -872,23 +1164,110 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
require.Len(t, toRemoveWindows, 3)
// update status of the moved host via its uuid (team has no profiles)
- err = ds.BulkSetPendingMDMHostProfiles(ctx, nil, nil, nil, []string{darwinHosts[1].UUID, windowsHosts[1].UUID})
+ err = ds.BulkSetPendingMDMHostProfiles(
+ ctx,
+ nil,
+ nil,
+ nil,
+ []string{darwinHosts[1].UUID, windowsHosts[1].UUID},
+ )
require.NoError(t, err)
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[0].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[1].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[2].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[3].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[4].Identifier,
+ },
},
darwinHosts[1]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[0].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[1].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[2].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[3].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[4].Identifier,
+ },
},
darwinHosts[2]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[0].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[1].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[2].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[3].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[4].Identifier,
+ },
},
unenrolledHost: {},
linuxHost: {},
@@ -896,9 +1275,21 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
// windows profiles are directly deleted without a pending state
windowsHosts[1]: {},
windowsHosts[2]: {
- {ProfileUUID: globalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: globalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: globalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
})
@@ -911,7 +1302,7 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
windowsConfigProfileForTest(t, "T1.1w", "T1.1"),
windowsConfigProfileForTest(t, "T1.2w", "T1.2"),
}
- err = ds.BatchSetMDMProfiles(ctx, &team1.ID, tm1DarwinProfiles, tm1WindowsProfiles)
+ err = ds.BatchSetMDMProfiles(ctx, &team1.ID, tm1DarwinProfiles, tm1WindowsProfiles, nil)
require.NoError(t, err)
tm1Profiles := getProfs(&team1.ID)
@@ -939,33 +1330,144 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
require.NoError(t, err)
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: tm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: tm1Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[0].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[1].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[2].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[3].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[4].Identifier,
+ },
+ {
+ ProfileUUID: tm1Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: tm1Profiles[0].Identifier,
+ },
+ {
+ ProfileUUID: tm1Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: tm1Profiles[1].Identifier,
+ },
},
darwinHosts[1]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[0].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[1].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[2].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[3].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[4].Identifier,
+ },
},
darwinHosts[2]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[0].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[1].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[2].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[3].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[4].Identifier,
+ },
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
- {ProfileUUID: tm1Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: tm1Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: tm1Profiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: tm1Profiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[1]: {},
windowsHosts[2]: {
- {ProfileUUID: globalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: globalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: globalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
})
@@ -983,15 +1485,15 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
// rows in this test since we don't have command uuids.
err = ds.BulkUpsertMDMAppleHostProfiles(ctx, []*fleet.MDMAppleBulkUpsertHostProfilePayload{
{
- HostUUID: darwinHosts[0].UUID, ProfileUUID: darwinGlobalProfiles[0].ProfileUUID,
+ HostUUID: darwinHosts[0].UUID, ProfileUUID: darwinGlobalProfiles[0].ProfileUUID, ProfileIdentifier: darwinGlobalProfiles[0].Identifier,
Status: &fleet.MDMDeliveryVerifying, OperationType: fleet.MDMOperationTypeRemove, Checksum: []byte("csum"),
},
{
- HostUUID: darwinHosts[0].UUID, ProfileUUID: darwinGlobalProfiles[1].ProfileUUID,
+ HostUUID: darwinHosts[0].UUID, ProfileUUID: darwinGlobalProfiles[1].ProfileUUID, ProfileIdentifier: darwinGlobalProfiles[1].Identifier,
Status: &fleet.MDMDeliveryVerifying, OperationType: fleet.MDMOperationTypeRemove, Checksum: []byte("csum"),
},
{
- HostUUID: darwinHosts[0].UUID, ProfileUUID: darwinGlobalProfiles[2].ProfileUUID,
+ HostUUID: darwinHosts[0].UUID, ProfileUUID: darwinGlobalProfiles[2].ProfileUUID, ProfileIdentifier: darwinGlobalProfiles[2].Identifier,
Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove, Checksum: []byte("csum"),
},
})
@@ -1007,7 +1509,7 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
windowsConfigProfileForTest(t, "T1.3w", "T1.3"),
}
- err = ds.BatchSetMDMProfiles(ctx, &team1.ID, newTm1DarwinProfiles, newTm1WindowsProfiles)
+ err = ds.BatchSetMDMProfiles(ctx, &team1.ID, newTm1DarwinProfiles, newTm1WindowsProfiles, nil)
require.NoError(t, err)
newTm1Profiles := getProfs(&team1.ID)
@@ -1019,32 +1521,138 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: tm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newTm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: tm1Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: tm1Profiles[0].Identifier,
+ },
+ {
+ ProfileUUID: newTm1Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: newTm1Profiles[0].Identifier,
+ },
+ {
+ ProfileUUID: newTm1Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: newTm1Profiles[1].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[3].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[1].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryFailed,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[4].Identifier,
+ },
},
darwinHosts[1]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[0].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[1].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[2].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[3].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[4].Identifier,
+ },
},
darwinHosts[2]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[0].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[1].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[2].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[3].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[4].Identifier,
+ },
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
- {ProfileUUID: newTm1Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newTm1Profiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[1]: {},
windowsHosts[2]: {
- {ProfileUUID: globalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: globalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: globalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
})
@@ -1063,7 +1671,7 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
windowsConfigProfileForTest(t, "T1.3w", "T1.3"),
}
- err = ds.BatchSetMDMProfiles(ctx, &team1.ID, newTm1DarwinProfiles, newTm1WindowsProfiles)
+ err = ds.BatchSetMDMProfiles(ctx, &team1.ID, newTm1DarwinProfiles, newTm1WindowsProfiles, nil)
require.NoError(t, err)
newTm1Profiles = getProfs(&team1.ID)
require.Len(t, newTm1Profiles, 6)
@@ -1074,33 +1682,143 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newTm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newTm1Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: newTm1Profiles[0].Identifier,
+ },
+ {
+ ProfileUUID: newTm1Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: newTm1Profiles[1].Identifier,
+ },
+ {
+ ProfileUUID: newTm1Profiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: newTm1Profiles[2].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[3].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[1].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryFailed,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[4].Identifier,
+ },
},
darwinHosts[1]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[0].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[1].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[2].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[3].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[4].Identifier,
+ },
},
darwinHosts[2]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[0].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[1].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[2].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[3].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: globalProfiles[4].Identifier,
+ },
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
- {ProfileUUID: newTm1Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newTm1Profiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[1]: {},
windowsHosts[2]: {
- {ProfileUUID: globalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: globalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: globalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: globalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
})
@@ -1117,7 +1835,8 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
windowsConfigProfileForTest(t, "G4w", "G4"),
}
- err = ds.BatchSetMDMProfiles(ctx, nil, newDarwinGlobalProfiles, newWindowsGlobalProfiles)
+ // TODO(roberto): add new darwin declarations for this and all subsequent assertions
+ err = ds.BatchSetMDMProfiles(ctx, nil, newDarwinGlobalProfiles, newWindowsGlobalProfiles, nil)
require.NoError(t, err)
newGlobalProfiles := getProfs(nil)
@@ -1127,36 +1846,112 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
err = ds.BulkSetPendingMDMHostProfiles(ctx, nil, []uint{0}, nil, nil)
require.NoError(t, err)
+ require.NoError(t, ds.MDMAppleStoreDDMStatusReport(ctx, darwinHosts[0].UUID, nil))
+ require.NoError(t, ds.MDMAppleStoreDDMStatusReport(ctx, darwinHosts[1].UUID, nil))
+ require.NoError(t, ds.MDMAppleStoreDDMStatusReport(ctx, darwinHosts[2].UUID, nil))
+
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newTm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryFailed,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[4].Identifier,
+ },
+ {
+ ProfileUUID: newTm1Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: newTm1Profiles[0].Identifier,
+ },
+ {
+ ProfileUUID: newTm1Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: newTm1Profiles[1].Identifier,
+ },
+ {
+ ProfileUUID: newTm1Profiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: newTm1Profiles[2].Identifier,
+ },
},
darwinHosts[1]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
},
darwinHosts[2]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
- {ProfileUUID: newTm1Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newTm1Profiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[1]: {},
windowsHosts[2]: {
- {ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
})
@@ -1176,7 +1971,7 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
windowsConfigProfileForTest(t, "G5w", "G5"),
}
- err = ds.BatchSetMDMProfiles(ctx, nil, newDarwinGlobalProfiles, newWindowsGlobalProfiles)
+ err = ds.BatchSetMDMProfiles(ctx, nil, newDarwinGlobalProfiles, newWindowsGlobalProfiles, nil)
require.NoError(t, err)
newGlobalProfiles = getProfs(nil)
require.Len(t, newGlobalProfiles, 8)
@@ -1188,35 +1983,107 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newTm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryFailed,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newTm1Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
darwinHosts[1]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
},
darwinHosts[2]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
- {ProfileUUID: newTm1Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newTm1Profiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[1]: {},
windowsHosts[2]: {
- {ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
})
@@ -1227,36 +2094,112 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newTm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryFailed,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newTm1Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
darwinHosts[1]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
},
darwinHosts[2]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
- {ProfileUUID: newTm1Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newTm1Profiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[1]: {},
windowsHosts[2]: {
- {ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[7].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
})
@@ -1270,7 +2213,7 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
windowsConfigProfileForTest(t, "T2.1w", "T2.1"),
}
- err = ds.BatchSetMDMProfiles(ctx, &team2.ID, tm2DarwinProfiles, tm2WindowsProfiles)
+ err = ds.BatchSetMDMProfiles(ctx, &team2.ID, tm2DarwinProfiles, tm2WindowsProfiles, nil)
require.NoError(t, err)
tm2Profiles := getProfs(&team2.ID)
require.Len(t, tm2Profiles, 2)
@@ -1281,39 +2224,123 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newTm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryFailed,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newTm1Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
darwinHosts[1]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: tm2Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: tm2Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
darwinHosts[2]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
- {ProfileUUID: newTm1Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newTm1Profiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[1]: {
- {ProfileUUID: tm2Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: tm2Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[2]: {
- {ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[7].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
})
@@ -1369,7 +2396,7 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
windowsConfigProfileForTest(t, "G7w", "G7", labels[5]),
}
- err = ds.BatchSetMDMProfiles(ctx, nil, newDarwinGlobalProfiles, newWindowsGlobalProfiles)
+ err = ds.BatchSetMDMProfiles(ctx, nil, newDarwinGlobalProfiles, newWindowsGlobalProfiles, nil)
require.NoError(t, err)
newGlobalProfiles = getProfs(nil)
require.Len(t, newGlobalProfiles, 12)
@@ -1381,51 +2408,150 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
// simulate an entry with some values set to NULL
ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
- _, err := q.ExecContext(ctx, `UPDATE host_mdm_apple_profiles SET detail = NULL WHERE profile_uuid = ?`, globalProfiles[2].ProfileUUID)
+ _, err := q.ExecContext(
+ ctx,
+ `UPDATE host_mdm_apple_profiles SET detail = NULL WHERE profile_uuid = ?`,
+ globalProfiles[2].ProfileUUID,
+ )
return err
})
// do a sync of all hosts, should not change anything as no host is a member
// of the new label-based profiles (indices change due to new Apple and
// Windows profiles)
- err = ds.BulkSetPendingMDMHostProfiles(ctx, hostIDsFromHosts(append(darwinHosts, append(windowsHosts, unenrolledHost, linuxHost)...)...), nil, nil, nil)
+ err = ds.BulkSetPendingMDMHostProfiles(
+ ctx,
+ hostIDsFromHosts(
+ append(darwinHosts, append(windowsHosts, unenrolledHost, linuxHost)...)...),
+ nil,
+ nil,
+ nil,
+ )
require.NoError(t, err)
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newTm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryFailed,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newTm1Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
darwinHosts[1]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: tm2Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[0].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[2].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[4].Identifier,
+ },
+ {
+ ProfileUUID: tm2Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: tm2Profiles[0].Identifier,
+ },
},
darwinHosts[2]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
- {ProfileUUID: newTm1Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newTm1Profiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[1]: {
- {ProfileUUID: tm2Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: tm2Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[2]: {
- {ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[7].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[8].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[9].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[8].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[9].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
})
@@ -1459,135 +2585,422 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
// make the new Apple host a member of labels[0] and [1]
// make the new Windows host a member of labels[3] and [4]
err = ds.AsyncBatchInsertLabelMembership(ctx, [][2]uint{
- {labels[0].ID, darwinHosts[3].ID}, {labels[1].ID, darwinHosts[3].ID},
- {labels[3].ID, windowsHosts[3].ID}, {labels[4].ID, windowsHosts[3].ID},
+ {labels[0].ID, darwinHosts[3].ID},
+ {labels[1].ID, darwinHosts[3].ID},
+ {labels[3].ID, windowsHosts[3].ID},
+ {labels[4].ID, windowsHosts[3].ID},
})
require.NoError(t, err)
// do a full sync, the new global hosts get the standard global profiles and
// also the label-based profile that they are a member of
- err = ds.BulkSetPendingMDMHostProfiles(ctx, hostIDsFromHosts(append(darwinHosts, append(windowsHosts, unenrolledHost, linuxHost)...)...), nil, nil, nil)
+ err = ds.BulkSetPendingMDMHostProfiles(
+ ctx,
+ hostIDsFromHosts(
+ append(darwinHosts, append(windowsHosts, unenrolledHost, linuxHost)...)...),
+ nil,
+ nil,
+ nil,
+ )
require.NoError(t, err)
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newTm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryFailed,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newTm1Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
darwinHosts[1]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: tm2Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: tm2Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
darwinHosts[2]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
darwinHosts[3]: {
- {ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
- {ProfileUUID: newTm1Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newTm1Profiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[1]: {
- {ProfileUUID: tm2Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: tm2Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[2]: {
- {ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[7].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[8].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[9].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[8].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[9].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[3]: {
- {ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[7].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[8].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[9].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[10].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[8].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[9].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[10].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
})
// make the darwinHosts[2] host a member of all labels
// make the windowsHosts[2] host a member of all labels
err = ds.AsyncBatchInsertLabelMembership(ctx, [][2]uint{
- {labels[0].ID, darwinHosts[2].ID}, {labels[1].ID, darwinHosts[2].ID}, {labels[2].ID, darwinHosts[2].ID},
- {labels[3].ID, darwinHosts[2].ID}, {labels[4].ID, darwinHosts[2].ID}, {labels[5].ID, darwinHosts[2].ID},
- {labels[0].ID, windowsHosts[2].ID}, {labels[1].ID, windowsHosts[2].ID}, {labels[2].ID, windowsHosts[2].ID},
- {labels[3].ID, windowsHosts[2].ID}, {labels[4].ID, windowsHosts[2].ID}, {labels[5].ID, windowsHosts[2].ID},
+ {labels[0].ID, darwinHosts[2].ID},
+ {labels[1].ID, darwinHosts[2].ID},
+ {labels[2].ID, darwinHosts[2].ID},
+ {labels[3].ID, darwinHosts[2].ID},
+ {labels[4].ID, darwinHosts[2].ID},
+ {labels[5].ID, darwinHosts[2].ID},
+ {labels[0].ID, windowsHosts[2].ID},
+ {labels[1].ID, windowsHosts[2].ID},
+ {labels[2].ID, windowsHosts[2].ID},
+ {labels[3].ID, windowsHosts[2].ID},
+ {labels[4].ID, windowsHosts[2].ID},
+ {labels[5].ID, windowsHosts[2].ID},
})
require.NoError(t, err)
// do a sync of those hosts, they will get the two label-based profiles of their platform
- err = ds.BulkSetPendingMDMHostProfiles(ctx, hostIDsFromHosts(darwinHosts[2], windowsHosts[2]), nil, nil, nil)
+ err = ds.BulkSetPendingMDMHostProfiles(
+ ctx,
+ hostIDsFromHosts(darwinHosts[2], windowsHosts[2]),
+ nil,
+ nil,
+ nil,
+ )
require.NoError(t, err)
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newTm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryFailed,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newTm1Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
darwinHosts[1]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: tm2Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: tm2Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
darwinHosts[2]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
darwinHosts[3]: {
- {ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
- {ProfileUUID: newTm1Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newTm1Profiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[1]: {
- {ProfileUUID: tm2Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: tm2Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[2]: {
- {ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[7].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[8].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[9].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[10].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[11].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[8].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[9].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[10].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[11].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[3]: {
- {ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[7].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[8].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[9].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[10].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[8].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[9].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[10].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
})
@@ -1596,131 +3009,427 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
require.NoError(t, ds.DeleteLabel(ctx, labels[3].Name))
// sync the affected profiles
- err = ds.BulkSetPendingMDMHostProfiles(ctx, nil, nil, []string{newGlobalProfiles[4].ProfileUUID}, nil)
+ err = ds.BulkSetPendingMDMHostProfiles(
+ ctx,
+ nil,
+ nil,
+ []string{newGlobalProfiles[4].ProfileUUID},
+ nil,
+ )
require.NoError(t, err)
- err = ds.BulkSetPendingMDMHostProfiles(ctx, nil, nil, []string{newGlobalProfiles[10].ProfileUUID}, nil)
+ err = ds.BulkSetPendingMDMHostProfiles(
+ ctx,
+ nil,
+ nil,
+ []string{newGlobalProfiles[10].ProfileUUID},
+ nil,
+ )
require.NoError(t, err)
// nothing changes - broken label-based profiles are simply ignored
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newTm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryFailed,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newTm1Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
darwinHosts[1]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: tm2Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: tm2Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
darwinHosts[2]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
darwinHosts[3]: {
- {ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
- {ProfileUUID: newTm1Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newTm1Profiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[1]: {
- {ProfileUUID: tm2Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: tm2Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[2]: {
- {ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[7].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[8].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[9].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[10].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[11].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[8].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[9].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[10].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[11].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[3]: {
- {ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[7].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[8].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[9].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[10].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[8].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[9].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[10].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
})
// update darwin/windows[2] so they are not members of labels[1][2] and [4][5], which
// should remove the G7 label-based profile, but not G6 as it is broken.
err = ds.AsyncBatchDeleteLabelMembership(ctx, [][2]uint{
- {labels[1].ID, darwinHosts[2].ID}, {labels[2].ID, darwinHosts[2].ID},
- {labels[4].ID, windowsHosts[2].ID}, {labels[5].ID, windowsHosts[2].ID},
+ {labels[1].ID, darwinHosts[2].ID},
+ {labels[2].ID, darwinHosts[2].ID},
+ {labels[4].ID, windowsHosts[2].ID},
+ {labels[5].ID, windowsHosts[2].ID},
})
require.NoError(t, err)
- err = ds.BulkSetPendingMDMHostProfiles(ctx, hostIDsFromHosts(darwinHosts[2], windowsHosts[2]), nil, nil, nil)
+ err = ds.BulkSetPendingMDMHostProfiles(
+ ctx,
+ hostIDsFromHosts(darwinHosts[2], windowsHosts[2]),
+ nil,
+ nil,
+ nil,
+ )
require.NoError(t, err)
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newTm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryFailed,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newTm1Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
darwinHosts[1]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: tm2Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: tm2Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
darwinHosts[2]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
},
darwinHosts[3]: {
- {ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
- {ProfileUUID: newTm1Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newTm1Profiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[1]: {
- {ProfileUUID: tm2Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: tm2Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[2]: {
- {ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[7].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[8].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[9].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[10].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[8].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[9].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[10].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[3]: {
- {ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[7].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[8].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[9].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[10].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[8].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[9].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[10].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
})
@@ -1729,62 +3438,206 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
setProfileLabels(t, newGlobalProfiles[4], labels[1])
setProfileLabels(t, newGlobalProfiles[10], labels[4])
- err = ds.BulkSetPendingMDMHostProfiles(ctx, nil, nil, []string{newGlobalProfiles[4].ProfileUUID}, nil)
+ err = ds.BulkSetPendingMDMHostProfiles(
+ ctx,
+ nil,
+ nil,
+ []string{newGlobalProfiles[4].ProfileUUID},
+ nil,
+ )
require.NoError(t, err)
- err = ds.BulkSetPendingMDMHostProfiles(ctx, nil, nil, []string{newGlobalProfiles[10].ProfileUUID}, nil)
+ err = ds.BulkSetPendingMDMHostProfiles(
+ ctx,
+ nil,
+ nil,
+ []string{newGlobalProfiles[10].ProfileUUID},
+ nil,
+ )
require.NoError(t, err)
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newTm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryFailed,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newTm1Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
darwinHosts[1]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: tm2Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: tm2Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
darwinHosts[2]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newGlobalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
},
darwinHosts[3]: {
- {ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
- {ProfileUUID: newTm1Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newTm1Profiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[1]: {
- {ProfileUUID: tm2Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: tm2Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[2]: {
- {ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[7].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[8].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[9].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[8].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[9].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[3]: {
- {ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[7].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[8].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[9].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[10].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[8].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[9].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[10].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
})
@@ -1798,7 +3651,7 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
windowsConfigProfileForTest(t, "T2.2w", "T2.2", labels[4], labels[5]),
}
- err = ds.BatchSetMDMProfiles(ctx, &team2.ID, tm2DarwinProfiles, tm2WindowsProfiles)
+ err = ds.BatchSetMDMProfiles(ctx, &team2.ID, tm2DarwinProfiles, tm2WindowsProfiles, nil)
require.NoError(t, err)
tm2Profiles = getProfs(&team2.ID)
require.Len(t, tm2Profiles, 4)
@@ -1813,62 +3666,196 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newTm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryFailed,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newTm1Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
darwinHosts[1]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: tm2Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: tm2Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
darwinHosts[2]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newGlobalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
},
darwinHosts[3]: {
- {ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
- {ProfileUUID: newTm1Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newTm1Profiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[1]: {
- {ProfileUUID: tm2Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: tm2Profiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[2]: {
- {ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[7].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[8].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[9].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[8].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[9].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[3]: {
- {ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[7].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[8].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[9].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[10].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[8].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[9].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[10].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
})
// make darwinHosts[1] and windowsHosts[1] members of the required labels
err = ds.AsyncBatchInsertLabelMembership(ctx, [][2]uint{
- {labels[1].ID, darwinHosts[1].ID}, {labels[2].ID, darwinHosts[1].ID},
- {labels[4].ID, windowsHosts[1].ID}, {labels[5].ID, windowsHosts[1].ID},
+ {labels[1].ID, darwinHosts[1].ID},
+ {labels[2].ID, darwinHosts[1].ID},
+ {labels[4].ID, windowsHosts[1].ID},
+ {labels[5].ID, windowsHosts[1].ID},
})
require.NoError(t, err)
@@ -1878,57 +3865,202 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newTm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryFailed,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newTm1Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
darwinHosts[1]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: tm2Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: tm2Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[0].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[2].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[4].Identifier,
+ },
+ {
+ ProfileUUID: tm2Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: tm2Profiles[0].Identifier,
+ },
+ {
+ ProfileUUID: tm2Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: tm2Profiles[1].Identifier,
+ },
},
darwinHosts[2]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newGlobalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
},
darwinHosts[3]: {
- {ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
- {ProfileUUID: newTm1Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newTm1Profiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[1]: {
- {ProfileUUID: tm2Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: tm2Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: tm2Profiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: tm2Profiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[2]: {
- {ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[7].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[8].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[9].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[8].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[9].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[3]: {
- {ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[7].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[8].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[9].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[10].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[8].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[9].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[10].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
})
@@ -1943,64 +4075,211 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newTm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryFailed,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newTm1Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
darwinHosts[1]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: tm2Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: tm2Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[0].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[2].Identifier,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ IdentifierOrName: globalProfiles[4].Identifier,
+ },
+ {
+ ProfileUUID: tm2Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: tm2Profiles[0].Identifier,
+ },
+ {
+ ProfileUUID: tm2Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ IdentifierOrName: tm2Profiles[1].Identifier,
+ },
},
darwinHosts[2]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newGlobalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
},
darwinHosts[3]: {
- {ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
- {ProfileUUID: newTm1Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newTm1Profiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[1]: {
- {ProfileUUID: tm2Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: tm2Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: tm2Profiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: tm2Profiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[2]: {
- {ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[7].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[8].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[9].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[8].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[9].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[3]: {
- {ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[7].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[8].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[9].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[10].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[8].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[9].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[10].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
})
// remove team 2 hosts membership from labels
err = ds.AsyncBatchDeleteLabelMembership(ctx, [][2]uint{
- {labels[1].ID, darwinHosts[1].ID}, {labels[2].ID, darwinHosts[1].ID},
- {labels[4].ID, windowsHosts[1].ID}, {labels[5].ID, windowsHosts[1].ID},
+ {labels[1].ID, darwinHosts[1].ID},
+ {labels[2].ID, darwinHosts[1].ID},
+ {labels[4].ID, windowsHosts[1].ID},
+ {labels[5].ID, windowsHosts[1].ID},
})
require.NoError(t, err)
@@ -2011,57 +4290,197 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newTm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryFailed,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newTm1Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
darwinHosts[1]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: tm2Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: tm2Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: tm2Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: tm2Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
darwinHosts[2]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newGlobalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
},
darwinHosts[3]: {
- {ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
- {ProfileUUID: newTm1Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newTm1Profiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[1]: {
- {ProfileUUID: tm2Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: tm2Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: tm2Profiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: tm2Profiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[2]: {
- {ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[7].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[8].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[9].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[8].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[9].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[3]: {
- {ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[7].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[8].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[9].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[10].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[8].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[9].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[10].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
})
@@ -2076,115 +4495,394 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newTm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryFailed,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newTm1Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
darwinHosts[1]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: tm2Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: tm2Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: tm2Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: tm2Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
},
darwinHosts[2]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newGlobalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
},
darwinHosts[3]: {
- {ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
- {ProfileUUID: newTm1Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newTm1Profiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[1]: {
- {ProfileUUID: tm2Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: tm2Profiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[2]: {
- {ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[7].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[8].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[9].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[8].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[9].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[3]: {
- {ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[7].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[8].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[9].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[10].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[8].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[9].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[10].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
})
// sanity-check, a full sync does not change anything
- err = ds.BulkSetPendingMDMHostProfiles(ctx, hostIDsFromHosts(append(darwinHosts, append(windowsHosts, unenrolledHost, linuxHost)...)...), nil, nil, nil)
+ err = ds.BulkSetPendingMDMHostProfiles(
+ ctx,
+ hostIDsFromHosts(
+ append(darwinHosts, append(windowsHosts, unenrolledHost, linuxHost)...)...),
+ nil,
+ nil,
+ nil,
+ )
require.NoError(t, err)
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newTm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryFailed,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newTm1Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
darwinHosts[1]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: tm2Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: tm2Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: globalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: globalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: tm2Profiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: tm2Profiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
},
darwinHosts[2]: {
- {ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
- {ProfileUUID: newGlobalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
+ {
+ ProfileUUID: globalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeRemove,
+ },
},
darwinHosts[3]: {
- {ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[0].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[1].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
- {ProfileUUID: newTm1Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newTm1Profiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newTm1Profiles[3].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[4].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newTm1Profiles[5].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[1]: {
- {ProfileUUID: tm2Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: tm2Profiles[2].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[2]: {
- {ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[7].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[8].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[9].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[8].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[9].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
windowsHosts[3]: {
- {ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[7].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[8].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[9].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
- {ProfileUUID: newGlobalProfiles[10].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {
+ ProfileUUID: newGlobalProfiles[6].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[7].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[8].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[9].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
+ {
+ ProfileUUID: newGlobalProfiles[10].ProfileUUID,
+ Status: &fleet.MDMDeliveryPending,
+ OperationType: fleet.MDMOperationTypeInstall,
+ },
},
})
}
@@ -2221,7 +4919,7 @@ func testGetHostMDMProfilesExpectedForVerification(t *testing.T, ds *Datastore)
configProfileForTest(t, "T1.2", "T1.2", "e"),
}
- err = ds.BatchSetMDMProfiles(ctx, &team.ID, profiles, nil)
+ err = ds.BatchSetMDMProfiles(ctx, &team.ID, profiles, nil, nil)
require.NoError(t, err)
profs, _, err := ds.ListMDMConfigProfiles(ctx, &team.ID, fleet.ListOptions{})
@@ -2259,7 +4957,7 @@ func testGetHostMDMProfilesExpectedForVerification(t *testing.T, ds *Datastore)
label, err := ds.NewLabel(ctx, &fleet.Label{Name: "test_label_1"})
require.NoError(t, err)
- err = ds.BatchSetMDMProfiles(ctx, &team.ID, profiles, nil)
+ err = ds.BatchSetMDMProfiles(ctx, &team.ID, profiles, nil, nil)
require.NoError(t, err)
var uid string
@@ -2332,7 +5030,7 @@ func testGetHostMDMProfilesExpectedForVerification(t *testing.T, ds *Datastore)
testLabel3, err := ds.NewLabel(ctx, &fleet.Label{Name: "test_label_3"})
require.NoError(t, err)
- err = ds.BatchSetMDMProfiles(ctx, &team.ID, profiles, nil)
+ err = ds.BatchSetMDMProfiles(ctx, &team.ID, profiles, nil, nil)
require.NoError(t, err)
var uid string
@@ -2416,7 +5114,7 @@ func testGetHostMDMProfilesExpectedForVerification(t *testing.T, ds *Datastore)
testLabel4, err := ds.NewLabel(ctx, &fleet.Label{Name: "test_label_4"})
require.NoError(t, err)
- err = ds.BatchSetMDMProfiles(ctx, &team.ID, profiles, nil)
+ err = ds.BatchSetMDMProfiles(ctx, &team.ID, profiles, nil, nil)
require.NoError(t, err)
var uid string
@@ -2490,7 +5188,7 @@ func testGetHostMDMProfilesExpectedForVerification(t *testing.T, ds *Datastore)
windowsConfigProfileForTest(t, "T5.2", "T5.2"),
}
- err = ds.BatchSetMDMProfiles(ctx, &team.ID, nil, profiles)
+ err = ds.BatchSetMDMProfiles(ctx, &team.ID, nil, profiles, nil)
require.NoError(t, err)
profs, _, err := ds.ListMDMConfigProfiles(ctx, &team.ID, fleet.ListOptions{})
@@ -2528,7 +5226,7 @@ func testGetHostMDMProfilesExpectedForVerification(t *testing.T, ds *Datastore)
label, err := ds.NewLabel(ctx, &fleet.Label{Name: "test_label_6"})
require.NoError(t, err)
- err = ds.BatchSetMDMProfiles(ctx, &team.ID, nil, profiles)
+ err = ds.BatchSetMDMProfiles(ctx, &team.ID, nil, profiles, nil)
require.NoError(t, err)
var uid string
@@ -2601,7 +5299,7 @@ func testGetHostMDMProfilesExpectedForVerification(t *testing.T, ds *Datastore)
testLabel3, err := ds.NewLabel(ctx, &fleet.Label{Name: uuid.NewString()})
require.NoError(t, err)
- err = ds.BatchSetMDMProfiles(ctx, &team.ID, nil, profiles)
+ err = ds.BatchSetMDMProfiles(ctx, &team.ID, nil, profiles, nil)
require.NoError(t, err)
var uid string
@@ -2685,7 +5383,7 @@ func testGetHostMDMProfilesExpectedForVerification(t *testing.T, ds *Datastore)
label, err := ds.NewLabel(ctx, &fleet.Label{Name: uuid.NewString()})
require.NoError(t, err)
- err = ds.BatchSetMDMProfiles(ctx, &team.ID, nil, profiles)
+ err = ds.BatchSetMDMProfiles(ctx, &team.ID, nil, profiles, nil)
require.NoError(t, err)
var uid string
@@ -3076,7 +5774,7 @@ func testBatchSetMDMProfilesTransactionError(t *testing.T, ds *Datastore) {
windowsConfigProfileForTest(t, "W2", "l2"),
}
// set the initial profiles without error
- err := ds.BatchSetMDMProfiles(ctx, nil, appleProfs, winProfs)
+ err := ds.BatchSetMDMProfiles(ctx, nil, appleProfs, winProfs, nil)
require.NoError(t, err)
// now ensure all steps are required (add a profile, delete a profile, set labels)
@@ -3092,7 +5790,7 @@ func testBatchSetMDMProfilesTransactionError(t *testing.T, ds *Datastore) {
ds.testBatchSetMDMAppleProfilesErr = c.appleErr
ds.testBatchSetMDMWindowsProfilesErr = c.windowsErr
- err = ds.BatchSetMDMProfiles(ctx, nil, appleProfs, winProfs)
+ err = ds.BatchSetMDMProfiles(ctx, nil, appleProfs, winProfs, nil)
require.ErrorContains(t, err, c.wantErr)
})
}
@@ -3175,7 +5873,8 @@ func testSCEPRenewalHelpers(t *testing.T, ds *Datastore) {
// use the host UUID, just to make sure they're
// different from each other, we don't care about the
// DER contents here
- Raw: []byte(h.UUID)}
+ Raw: []byte(h.UUID),
+ }
err = scepDepot.Put(cert.Subject.CommonName, cert)
require.NoError(t, err)
req := mdm.Request{
@@ -3275,3 +5974,372 @@ func testSCEPRenewalHelpers(t *testing.T, ds *Datastore) {
require.NoError(t, err)
checkSCEPRenew(assocs[0], nil)
}
+
+func testMDMProfilesSummaryAndHostFilters(t *testing.T, ds *Datastore) {
+ // TODO: Expand this test to include:
+ // - more scenarios for windows
+ // - disk encryption (mac and windows)
+ // - more scenarios for labels
+
+ ctx := context.Background()
+
+ checkSummaryWindows := func(t *testing.T, teamID *uint, expected fleet.MDMProfilesSummary) {
+ ps, err := ds.GetMDMWindowsProfilesSummary(ctx, teamID)
+ require.NoError(t, err)
+ require.NotNil(t, ps)
+ require.Equal(t, expected, *ps)
+ }
+
+ checkSummaryMac := func(t *testing.T, teamID *uint, expected fleet.MDMProfilesSummary) {
+ ps, err := ds.GetMDMAppleProfilesSummary(ctx, teamID)
+ require.NoError(t, err)
+ require.NotNil(t, ps)
+ require.Equal(t, expected, *ps)
+ }
+
+ checkListHostsFilterOSSettings := func(t *testing.T, teamID *uint, status fleet.OSSettingsStatus, expectedIDs []uint) {
+ gotHosts, err := ds.ListHosts(ctx, fleet.TeamFilter{User: test.UserAdmin}, fleet.HostListOptions{TeamFilter: teamID, OSSettingsFilter: status})
+ require.NoError(t, err)
+ if len(expectedIDs) != len(gotHosts) {
+ gotIDs := make([]uint, len(gotHosts))
+ for _, h := range gotHosts {
+ gotIDs = append(gotIDs, h.ID)
+ }
+ require.Len(t, gotHosts, len(expectedIDs), fmt.Sprintf("status: %s expected: %v got: %v", status, expectedIDs, gotIDs))
+
+ }
+ for _, h := range gotHosts {
+ require.Contains(t, expectedIDs, h.ID)
+ }
+
+ count, err := ds.CountHosts(ctx, fleet.TeamFilter{User: test.UserAdmin}, fleet.HostListOptions{TeamFilter: teamID, OSSettingsFilter: status})
+ require.NoError(t, err)
+ require.Equal(t, len(expectedIDs), count, "status: %s", status)
+ }
+
+ type hostIDsByProfileStatus map[fleet.MDMDeliveryStatus][]uint
+
+ checkExpected := func(t *testing.T, teamID *uint, ep hostIDsByProfileStatus) {
+ expectSummaryWindows := map[fleet.MDMDeliveryStatus]uint{}
+ expectSummaryMac := map[fleet.MDMDeliveryStatus]uint{}
+ for status, ids := range ep {
+ if len(ids) > 0 {
+ for _, id := range ids {
+ if id < 5 {
+ expectSummaryWindows[status]++
+ } else {
+ expectSummaryMac[status]++
+ }
+ }
+ }
+ }
+ checkSummaryMac(t, teamID, fleet.MDMProfilesSummary{
+ Pending: expectSummaryMac[fleet.MDMDeliveryPending],
+ Failed: expectSummaryMac[fleet.MDMDeliveryFailed],
+ Verifying: expectSummaryMac[fleet.MDMDeliveryVerifying],
+ Verified: expectSummaryMac[fleet.MDMDeliveryVerified],
+ })
+
+ checkSummaryWindows(t, teamID, fleet.MDMProfilesSummary{
+ Pending: expectSummaryWindows[fleet.MDMDeliveryPending],
+ Failed: expectSummaryWindows[fleet.MDMDeliveryFailed],
+ Verifying: expectSummaryWindows[fleet.MDMDeliveryVerifying],
+ Verified: expectSummaryWindows[fleet.MDMDeliveryVerified],
+ })
+
+ checkListHostsFilterOSSettings(t, teamID, fleet.OSSettingsVerified, ep[fleet.MDMDeliveryVerified])
+ checkListHostsFilterOSSettings(t, teamID, fleet.OSSettingsVerifying, ep[fleet.MDMDeliveryVerifying])
+ checkListHostsFilterOSSettings(t, teamID, fleet.OSSettingsFailed, ep[fleet.MDMDeliveryFailed])
+ checkListHostsFilterOSSettings(t, teamID, fleet.OSSettingsPending, ep[fleet.MDMDeliveryPending])
+ }
+
+ // checkWinHostProfiles := func(t *testing.T, hostUUID string, statusByProfUUID map[string]string) {
+ // profs, err := ds.GetHostMDMWindowsProfiles(ctx, hostUUID)
+ // require.NoError(t, err)
+ // require.Len(t, profs, len(statusByProfUUID))
+ // for _, prof := range profs {
+ // ep, ok := statusByProfUUID[prof.ProfileUUID]
+ // require.True(t, ok)
+ // require.Equal(t, ep, prof.Status)
+ // }
+ // }
+
+ checkMacHostProfiles := func(t *testing.T, hostUUID string, statusByProfUUID map[string]string) {
+ profs, err := ds.GetHostMDMAppleProfiles(ctx, hostUUID)
+ require.NoError(t, err)
+ require.Len(t, profs, len(statusByProfUUID))
+ for _, prof := range profs {
+ ep, ok := statusByProfUUID[prof.ProfileUUID]
+ require.True(t, ok)
+ require.NotNil(t, prof.Status)
+ require.Equal(t, fleet.MDMDeliveryStatus(ep), *prof.Status)
+ }
+ }
+
+ upsertHostProfileStatus := func(t *testing.T, hostUUID string, profUUID string, status *fleet.MDMDeliveryStatus) {
+ ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
+ var table string
+ var profType string
+ switch {
+ case strings.HasPrefix(profUUID, "a"):
+ table = "host_mdm_apple_profiles"
+ profType = "profile"
+ case strings.HasPrefix(profUUID, "w"):
+ table = "host_mdm_windows_profiles"
+ profType = "profile"
+ case strings.HasPrefix(profUUID, "d"):
+ table = "host_mdm_apple_declarations"
+ profType = "declaration"
+ default:
+ require.FailNow(t, "unknown profile type")
+ }
+ stmt := fmt.Sprintf(`INSERT INTO %s (host_uuid, %s_uuid, status) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE status = ?`, table, profType)
+ _, err := q.ExecContext(ctx, stmt, hostUUID, profUUID, status, status)
+ if err != nil {
+ require.NoError(t, err)
+ return err
+ }
+ stmt = fmt.Sprintf(`UPDATE %s SET operation_type = ? WHERE host_uuid = ? AND %s_uuid = ?`, table, profType)
+ _, err = q.ExecContext(ctx, stmt, fleet.MDMOperationTypeInstall, hostUUID, profUUID)
+ require.NoError(t, err)
+ return err
+ })
+ }
+
+ cleanupTables := func(t *testing.T) {
+ ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
+ _, err := q.ExecContext(ctx, `DELETE FROM host_mdm_windows_profiles`)
+ return err
+ })
+ ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
+ _, err := q.ExecContext(ctx, `DELETE FROM host_mdm_apple_profiles`)
+ return err
+ })
+ ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
+ _, err := q.ExecContext(ctx, `DELETE FROM host_mdm_apple_declarations`)
+ return err
+ })
+ ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
+ _, err := q.ExecContext(ctx, `DELETE FROM host_disk_encryption_keys`)
+ return err
+ })
+ ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
+ _, err := q.ExecContext(ctx, `DELETE FROM host_disks`)
+ return err
+ })
+ }
+
+ // updateHostDisks := func(t *testing.T, hostID uint, encrypted bool, updated_at time.Time) {
+ // ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
+ // stmt := `UPDATE host_disks SET encrypted = ?, updated_at = ? where host_id = ?`
+ // _, err := q.ExecContext(ctx, stmt, encrypted, updated_at, hostID)
+ // return err
+ // })
+ // }
+
+ // Create some hosts
+ var hosts []*fleet.Host
+ macHostsByID := make(map[uint]*fleet.Host, 5)
+ winHostsByID := make(map[uint]*fleet.Host, 5)
+ for i := 0; i < 10; i++ {
+ p := "windows"
+ if i >= 5 {
+ p = "darwin"
+ }
+ u := uuid.New().String()
+ h, err := ds.NewHost(ctx, &fleet.Host{
+ DetailUpdatedAt: time.Now(),
+ LabelUpdatedAt: time.Now(),
+ PolicyUpdatedAt: time.Now(),
+ SeenTime: time.Now(),
+ NodeKey: &u,
+ UUID: u,
+ Hostname: u,
+ Platform: p,
+ })
+ require.NoError(t, err)
+ require.NotNil(t, h)
+ hosts = append(hosts, h)
+ if p == "darwin" {
+ macHostsByID[h.ID] = h
+ } else {
+ winHostsByID[h.ID] = h
+ }
+
+ require.NoError(t, ds.SetOrUpdateMDMData(ctx, h.ID, false, true, "https://example.com", false, fleet.WellKnownMDMFleet, ""))
+ }
+
+ checkExpected(t, nil, nil)
+
+ upsertHostProfileStatus(t, hosts[0].UUID, "w1", &fleet.MDMDeliveryPending)
+
+ checkExpected(t, nil, hostIDsByProfileStatus{
+ fleet.MDMDeliveryPending: []uint{hosts[0].ID},
+ })
+
+ // add some mac profiles with different statuses
+ upsertHostProfileStatus(t, hosts[9].UUID, "a1", &fleet.MDMDeliveryFailed)
+ upsertHostProfileStatus(t, hosts[9].UUID, "a2", &fleet.MDMDeliveryPending)
+ upsertHostProfileStatus(t, hosts[9].UUID, "a3", &fleet.MDMDeliveryVerifying)
+ upsertHostProfileStatus(t, hosts[9].UUID, "a4", &fleet.MDMDeliveryVerified)
+
+ // add some mac declarations with different statuses
+ upsertHostProfileStatus(t, hosts[9].UUID, "d1", &fleet.MDMDeliveryFailed)
+ upsertHostProfileStatus(t, hosts[9].UUID, "d2", &fleet.MDMDeliveryPending)
+ upsertHostProfileStatus(t, hosts[9].UUID, "d3", &fleet.MDMDeliveryVerifying)
+ upsertHostProfileStatus(t, hosts[9].UUID, "d4", &fleet.MDMDeliveryVerified)
+
+ checkExpected(t, nil, hostIDsByProfileStatus{
+ fleet.MDMDeliveryPending: []uint{hosts[0].ID},
+ fleet.MDMDeliveryFailed: []uint{hosts[9].ID},
+ })
+ expectedHostProfiles := map[string]string{
+ "a1": "failed",
+ "a2": "pending",
+ "a3": "verifying",
+ "a4": "verified",
+ "d1": "failed",
+ "d2": "pending",
+ "d3": "verifying",
+ "d4": "verified",
+ }
+ checkMacHostProfiles(t, hosts[9].UUID, expectedHostProfiles)
+
+ // set failed mac profile to pending, still failed because of failed declaration
+ upsertHostProfileStatus(t, hosts[9].UUID, "a1", &fleet.MDMDeliveryPending)
+ expectedHostProfiles["a1"] = "pending"
+ checkExpected(t, nil, hostIDsByProfileStatus{
+ fleet.MDMDeliveryPending: []uint{hosts[0].ID},
+ fleet.MDMDeliveryFailed: []uint{hosts[9].ID},
+ })
+ checkMacHostProfiles(t, hosts[9].UUID, expectedHostProfiles)
+
+ // set failed mac declaration to pending, now host stsatus is pending
+ upsertHostProfileStatus(t, hosts[9].UUID, "d1", &fleet.MDMDeliveryPending)
+ expectedHostProfiles["d1"] = "pending"
+ checkExpected(t, nil, hostIDsByProfileStatus{
+ fleet.MDMDeliveryPending: []uint{hosts[0].ID, hosts[9].ID},
+ })
+ checkMacHostProfiles(t, hosts[9].UUID, expectedHostProfiles)
+
+ // set pending mac declaration to failed, host status is now failed
+ upsertHostProfileStatus(t, hosts[9].UUID, "d2", &fleet.MDMDeliveryFailed)
+ expectedHostProfiles["d2"] = "failed"
+ checkExpected(t, nil, hostIDsByProfileStatus{
+ fleet.MDMDeliveryPending: []uint{hosts[0].ID},
+ fleet.MDMDeliveryFailed: []uint{hosts[9].ID},
+ })
+ checkMacHostProfiles(t, hosts[9].UUID, expectedHostProfiles)
+
+ // set failed mac declaration to verifying, host status is now pending
+ upsertHostProfileStatus(t, hosts[9].UUID, "d2", &fleet.MDMDeliveryVerifying)
+ expectedHostProfiles["d2"] = "verifying"
+ checkExpected(t, nil, hostIDsByProfileStatus{
+ fleet.MDMDeliveryPending: []uint{hosts[0].ID, hosts[9].ID},
+ })
+ checkMacHostProfiles(t, hosts[9].UUID, expectedHostProfiles)
+
+ // set pending mac profiles to verifying, host status is still pending because d1 is still pending
+ upsertHostProfileStatus(t, hosts[9].UUID, "a1", &fleet.MDMDeliveryVerifying)
+ expectedHostProfiles["a1"] = "verifying"
+ upsertHostProfileStatus(t, hosts[9].UUID, "a2", &fleet.MDMDeliveryVerifying)
+ expectedHostProfiles["a2"] = "verifying"
+ checkExpected(t, nil, hostIDsByProfileStatus{
+ fleet.MDMDeliveryPending: []uint{hosts[0].ID, hosts[9].ID},
+ })
+ checkMacHostProfiles(t, hosts[9].UUID, expectedHostProfiles)
+
+ // set pending mac declarations to verifying, host status is now verifying
+ upsertHostProfileStatus(t, hosts[9].UUID, "d1", &fleet.MDMDeliveryVerifying)
+ expectedHostProfiles["d1"] = "verifying"
+ checkExpected(t, nil, hostIDsByProfileStatus{
+ fleet.MDMDeliveryPending: []uint{hosts[0].ID},
+ fleet.MDMDeliveryVerifying: []uint{hosts[9].ID},
+ })
+ checkMacHostProfiles(t, hosts[9].UUID, expectedHostProfiles)
+
+ // set a mac profile to failed, host status is now failed
+ upsertHostProfileStatus(t, hosts[9].UUID, "a1", &fleet.MDMDeliveryFailed)
+ expectedHostProfiles["a1"] = "failed"
+ checkExpected(t, nil, hostIDsByProfileStatus{
+ fleet.MDMDeliveryPending: []uint{hosts[0].ID},
+ fleet.MDMDeliveryFailed: []uint{hosts[9].ID},
+ })
+ checkMacHostProfiles(t, hosts[9].UUID, expectedHostProfiles)
+
+ // set mac profiles to verified, host status is now verifying because declarations are still
+ // verifying
+ upsertHostProfileStatus(t, hosts[9].UUID, "a1", &fleet.MDMDeliveryVerified)
+ expectedHostProfiles["a1"] = "verified"
+ upsertHostProfileStatus(t, hosts[9].UUID, "a2", &fleet.MDMDeliveryVerified)
+ expectedHostProfiles["a2"] = "verified"
+ upsertHostProfileStatus(t, hosts[9].UUID, "a3", &fleet.MDMDeliveryVerified)
+ expectedHostProfiles["a3"] = "verified"
+ upsertHostProfileStatus(t, hosts[9].UUID, "a4", &fleet.MDMDeliveryVerified)
+ expectedHostProfiles["a4"] = "verified"
+ checkExpected(t, nil, hostIDsByProfileStatus{
+ fleet.MDMDeliveryPending: []uint{hosts[0].ID},
+ fleet.MDMDeliveryVerifying: []uint{hosts[9].ID},
+ })
+ checkMacHostProfiles(t, hosts[9].UUID, expectedHostProfiles)
+
+ // set mac declarations to verified, host status is now verified
+ upsertHostProfileStatus(t, hosts[9].UUID, "d1", &fleet.MDMDeliveryVerified)
+ expectedHostProfiles["d1"] = "verified"
+ upsertHostProfileStatus(t, hosts[9].UUID, "d2", &fleet.MDMDeliveryVerified)
+ expectedHostProfiles["d2"] = "verified"
+ upsertHostProfileStatus(t, hosts[9].UUID, "d3", &fleet.MDMDeliveryVerified)
+ expectedHostProfiles["d3"] = "verified"
+ checkExpected(t, nil, hostIDsByProfileStatus{
+ fleet.MDMDeliveryPending: []uint{hosts[0].ID},
+ fleet.MDMDeliveryVerified: []uint{hosts[9].ID},
+ })
+ checkMacHostProfiles(t, hosts[9].UUID, expectedHostProfiles)
+
+ // set a mac declaration to nil, host status is now pending
+ upsertHostProfileStatus(t, hosts[9].UUID, "d1", nil)
+ expectedHostProfiles["d1"] = "pending"
+ checkExpected(t, nil, hostIDsByProfileStatus{
+ fleet.MDMDeliveryPending: []uint{hosts[0].ID, hosts[9].ID},
+ })
+ checkMacHostProfiles(t, hosts[9].UUID, expectedHostProfiles)
+
+ // works as expected if we remove mac declarations
+ ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
+ _, err := q.ExecContext(ctx, `DELETE FROM host_mdm_apple_declarations`)
+ return err
+ })
+ delete(expectedHostProfiles, "d1")
+ delete(expectedHostProfiles, "d2")
+ delete(expectedHostProfiles, "d3")
+ delete(expectedHostProfiles, "d4")
+ checkExpected(t, nil, hostIDsByProfileStatus{
+ fleet.MDMDeliveryPending: []uint{hosts[0].ID},
+ fleet.MDMDeliveryVerified: []uint{hosts[9].ID}, // all profiles were verified
+ })
+ checkMacHostProfiles(t, hosts[9].UUID, expectedHostProfiles)
+
+ // works as expected if we remove mac profiles
+ ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
+ _, err := q.ExecContext(ctx, `DELETE FROM host_mdm_apple_profiles`)
+ return err
+ })
+ delete(expectedHostProfiles, "a1")
+ delete(expectedHostProfiles, "a2")
+ delete(expectedHostProfiles, "a3")
+ delete(expectedHostProfiles, "a4")
+ checkExpected(t, nil, hostIDsByProfileStatus{
+ fleet.MDMDeliveryPending: []uint{hosts[0].ID},
+ })
+ checkMacHostProfiles(t, hosts[9].UUID, expectedHostProfiles)
+
+ // works as expected if declarations but no profiles
+ upsertHostProfileStatus(t, hosts[9].UUID, "d1", &fleet.MDMDeliveryPending)
+ expectedHostProfiles["d1"] = "pending"
+ checkExpected(t, nil, hostIDsByProfileStatus{
+ fleet.MDMDeliveryPending: []uint{hosts[0].ID, hosts[9].ID},
+ })
+ checkMacHostProfiles(t, hosts[9].UUID, expectedHostProfiles)
+
+ cleanupTables(t)
+}
diff --git a/server/datastore/mysql/microsoft_mdm.go b/server/datastore/mysql/microsoft_mdm.go
index 9cd2a784db..e8904bb5a9 100644
--- a/server/datastore/mysql/microsoft_mdm.go
+++ b/server/datastore/mysql/microsoft_mdm.go
@@ -734,7 +734,7 @@ WHERE
return nil, ctxerr.Wrap(ctx, err, "get mdm windows config profile")
}
- labels, err := ds.listProfileLabelsForProfiles(ctx, []string{res.ProfileUUID}, nil)
+ labels, err := ds.listProfileLabelsForProfiles(ctx, []string{res.ProfileUUID}, nil, nil)
if err != nil {
return nil, err
}
@@ -1495,6 +1495,8 @@ INSERT INTO
(SELECT ?, ?, ?, ?, CURRENT_TIMESTAMP() FROM DUAL WHERE
NOT EXISTS (
SELECT 1 FROM mdm_apple_configuration_profiles WHERE name = ? AND team_id = ?
+ ) AND NOT EXISTS (
+ SELECT 1 FROM mdm_apple_declarations WHERE name = ? AND team_id = ?
)
)`
@@ -1504,7 +1506,7 @@ INSERT INTO
}
err := ds.withTx(ctx, func(tx sqlx.ExtContext) error {
- res, err := tx.ExecContext(ctx, insertProfileStmt, profileUUID, teamID, cp.Name, cp.SyncML, cp.Name, teamID)
+ res, err := tx.ExecContext(ctx, insertProfileStmt, profileUUID, teamID, cp.Name, cp.SyncML, cp.Name, teamID, cp.Name, teamID)
if err != nil {
switch {
case isDuplicate(err):
@@ -1556,6 +1558,8 @@ INSERT INTO
(SELECT ?, ?, ?, ?, CURRENT_TIMESTAMP() FROM DUAL WHERE
NOT EXISTS (
SELECT 1 FROM mdm_apple_configuration_profiles WHERE name = ? AND team_id = ?
+ ) AND NOT EXISTS (
+ SELECT 1 FROM mdm_apple_declarations WHERE name = ? AND team_id = ?
)
)
ON DUPLICATE KEY UPDATE
@@ -1568,7 +1572,7 @@ ON DUPLICATE KEY UPDATE
teamID = *cp.TeamID
}
- res, err := ds.writer(ctx).ExecContext(ctx, stmt, profileUUID, teamID, cp.Name, cp.SyncML, cp.Name, teamID)
+ res, err := ds.writer(ctx).ExecContext(ctx, stmt, profileUUID, teamID, cp.Name, cp.SyncML, cp.Name, teamID, cp.Name, teamID)
if err != nil {
switch {
case isDuplicate(err):
diff --git a/server/datastore/mysql/migrations/tables/20240327115530_AddDDMTables.go b/server/datastore/mysql/migrations/tables/20240327115530_AddDDMTables.go
new file mode 100644
index 0000000000..80bf0b0208
--- /dev/null
+++ b/server/datastore/mysql/migrations/tables/20240327115530_AddDDMTables.go
@@ -0,0 +1,135 @@
+package tables
+
+import (
+ "database/sql"
+ "fmt"
+)
+
+func init() {
+ MigrationClient.AddMigration(Up_20240327115530, Down_20240327115530)
+}
+
+func Up_20240327115530(tx *sql.Tx) error {
+ _, err := tx.Exec(`
+CREATE TABLE mdm_apple_declarations (
+ -- declaration_uuid is used as the primary key of the declaration
+ declaration_uuid varchar(37) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
+
+ -- team_id references the team that owns this declaration
+ team_id int(10) unsigned NOT NULL DEFAULT '0',
+
+ -- identifier is the "Identifier" field in the declaration, surfaced for convenience.
+ identifier varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+
+ -- name is the name of the declaration
+ name varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+
+ -- raw_json contains a JSON blob with the declaration contents
+ raw_json json NOT NULL,
+
+ -- checksum is an MD5 checksum of the declaration, in binary form
+ checksum binary(16) NOT NULL,
+
+ created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ uploaded_at timestamp NULL DEFAULT NULL,
+
+ PRIMARY KEY (declaration_uuid),
+ UNIQUE KEY idx_mdm_apple_declaration_team_identifier (team_id, identifier),
+ UNIQUE KEY idx_mdm_apple_declaration_team_name (team_id, name)
+)
+ `)
+ if err != nil {
+ return fmt.Errorf("creating mdm_apple_declarations table: %w", err)
+ }
+
+ _, err = tx.Exec(`
+CREATE TABLE mdm_declaration_labels (
+ -- id is used as the primary key of this table
+ id int(10) unsigned NOT NULL AUTO_INCREMENT,
+
+ -- apple_declaration_uuid references a declaration
+ apple_declaration_uuid varchar(37) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
+
+
+ -- label name is stored here because we need to list the labels in the UI
+ -- even if it has been deleted from the labels table.
+ label_name varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+
+ -- label id is nullable in case it gets deleted from the labels table.
+ -- A row in this table with label_id = null indicates the "broken" state
+ label_id int(10) unsigned DEFAULT NULL,
+
+ created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ uploaded_at timestamp NULL DEFAULT NULL,
+
+ PRIMARY KEY (id),
+ UNIQUE KEY idx_mdm_declaration_labels_label_name (apple_declaration_uuid, label_name),
+ KEY label_id (label_id),
+ CONSTRAINT mdm_declaration_labels_ibfk_1 FOREIGN KEY (apple_declaration_uuid) REFERENCES mdm_apple_declarations (declaration_uuid) ON DELETE CASCADE,
+ CONSTRAINT mdm_declaration_labels_ibfk_3 FOREIGN KEY (label_id) REFERENCES labels (id) ON DELETE SET NULL
+)
+ `)
+ if err != nil {
+ return fmt.Errorf("creating mdm_declaration_labels table: %w", err)
+ }
+
+ _, err = tx.Exec(`
+CREATE TABLE mdm_apple_declaration_activation_references (
+ -- declaration_uuid is the declaration that contains the references
+ declaration_uuid varchar(37) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
+
+ -- reference is the declaration_uuid of another declaration
+ reference varchar(37) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
+
+ PRIMARY KEY (declaration_uuid, reference),
+ CONSTRAINT FOREIGN KEY (declaration_uuid) REFERENCES mdm_apple_declarations (declaration_uuid) ON UPDATE CASCADE,
+ CONSTRAINT FOREIGN KEY (reference) REFERENCES mdm_apple_declarations (declaration_uuid) ON UPDATE CASCADE
+)
+ `)
+ if err != nil {
+ return fmt.Errorf("creating mdm_apple_declaration_activation_references table: %w", err)
+ }
+
+ _, err = tx.Exec(`
+CREATE TABLE host_mdm_apple_declarations (
+ -- host_uuid references a host in the hosts table
+ host_uuid varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+
+ -- status represents the status of the declaration in the host
+ status varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+
+ -- operation_type is used to signal if the declaration is being added, removed, etc
+ operation_type varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+
+ -- detail contains any messages or errors from the protocol or Fleet
+ detail text COLLATE utf8mb4_unicode_ci,
+
+ -- checksum of the currently implemented declaration
+ checksum binary(16) NOT NULL,
+
+ -- declaration_uuid references the declaration assigned to the host's team
+ declaration_uuid varchar(37) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
+
+ -- declaration_identifier is the identifier of the declaration
+ declaration_identifier varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+
+ -- declaration_name is the name of the declaration
+ declaration_name varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
+
+ PRIMARY KEY (host_uuid, declaration_uuid),
+ KEY status (status),
+ KEY operation_type (operation_type),
+ CONSTRAINT host_mdm_apple_declarations_ibfk_1 FOREIGN KEY (status) REFERENCES mdm_delivery_status (status) ON UPDATE CASCADE,
+ CONSTRAINT host_mdm_apple_declarations_ibfk_2 FOREIGN KEY (operation_type) REFERENCES mdm_operation_types (operation_type) ON UPDATE CASCADE
+)
+ `)
+ if err != nil {
+ return fmt.Errorf("creatign host_mdm_apple_declarations table %w", err)
+ }
+
+ return nil
+}
+
+func Down_20240327115530(tx *sql.Tx) error {
+ return nil
+}
diff --git a/server/datastore/mysql/migrations/tables/20240327115617_CreateTableNanoDDMRequests.go b/server/datastore/mysql/migrations/tables/20240327115617_CreateTableNanoDDMRequests.go
new file mode 100644
index 0000000000..2aa6de300b
--- /dev/null
+++ b/server/datastore/mysql/migrations/tables/20240327115617_CreateTableNanoDDMRequests.go
@@ -0,0 +1,36 @@
+package tables
+
+import (
+ "database/sql"
+ "fmt"
+)
+
+func init() {
+ MigrationClient.AddMigration(Up_20240327115617, Down_20240327115617)
+}
+
+func Up_20240327115617(tx *sql.Tx) error {
+ _, err := tx.Exec(`
+CREATE TABLE mdm_apple_declarative_requests (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ enrollment_id VARCHAR(255) NOT NULL,
+ -- Should be one of "tokens", "declaration-items", "status", or "declaration/…/…" where the ellipses reference a declaration on the server
+ message_type VARCHAR(255) NOT NULL,
+ -- json payload
+ raw_json TEXT,
+ PRIMARY KEY (id),
+ CONSTRAINT mdm_apple_declarative_requests_enrollment_id FOREIGN KEY (enrollment_id) REFERENCES nano_enrollments (id) ON DELETE CASCADE
+)
+`)
+
+ if err != nil {
+ return fmt.Errorf("creating mdm_apple_declarative_requsts: %w", err)
+ }
+
+ return nil
+}
+
+func Down_20240327115617(tx *sql.Tx) error {
+ return nil
+}
diff --git a/server/datastore/mysql/mysql.go b/server/datastore/mysql/mysql.go
index 8c4d7c62c2..866f9d6d1d 100644
--- a/server/datastore/mysql/mysql.go
+++ b/server/datastore/mysql/mysql.go
@@ -1281,3 +1281,57 @@ func (ds *Datastore) optimisticGetOrInsert(ctx context.Context, readStmt, insert
}
return id, nil
}
+
+// batchProcessDB abstracts the batch processing logic, for a given payload:
+//
+// - generateValueArgs will get called for each item, the expected return values are:
+// - a string containing the placeholders for each item in the batch
+// - a slice of arguments containing one item for each placeholder
+//
+// - executeBatch will get called on each batch to perform the operation in the db
+//
+// TODO(roberto): use this function in all the functions where we do ad-hoc
+// batch processing.
+func batchProcessDB[T any](
+ payload []T,
+ batchSize int,
+ generateValueArgs func(T) (string, []any),
+ executeBatch func(string, []any) error,
+) error {
+ if len(payload) == 0 {
+ return nil
+ }
+
+ var (
+ args []any
+ sb strings.Builder
+ batchCount int
+ )
+
+ resetBatch := func() {
+ batchCount = 0
+ args = args[:0]
+ sb.Reset()
+ }
+
+ for _, item := range payload {
+ valuePart, itemArgs := generateValueArgs(item)
+ args = append(args, itemArgs...)
+ sb.WriteString(valuePart)
+ batchCount++
+
+ if batchCount >= batchSize {
+ if err := executeBatch(sb.String(), args); err != nil {
+ return err
+ }
+ resetBatch()
+ }
+ }
+
+ if batchCount > 0 {
+ if err := executeBatch(sb.String(), args); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/server/datastore/mysql/mysql_test.go b/server/datastore/mysql/mysql_test.go
index 171426016d..de5eee8905 100644
--- a/server/datastore/mysql/mysql_test.go
+++ b/server/datastore/mysql/mysql_test.go
@@ -1229,3 +1229,55 @@ func TestWhereFilterGlobalOrTeamIDByTeams(t *testing.T) {
})
}
}
+
+func TestBatchProcessDB(t *testing.T) {
+ type testData struct {
+ id int
+ value string
+ }
+
+ payload := []interface{}{
+ &testData{id: 1, value: "a"},
+ &testData{id: 2, value: "b"},
+ &testData{id: 3, value: "c"},
+ }
+
+ generateValueArgs := func(item interface{}) (string, []any) {
+ p := item.(*testData)
+ valuePart := "(?, ?),"
+ args := []any{p.id, p.value}
+ return valuePart, args
+ }
+
+ t.Run("TestEmptyPayload", func(t *testing.T) {
+ executeBatch := func(valuePart string, args []any) error {
+ return errors.New("execute shouldn't be called for an empty payload")
+ }
+ err := batchProcessDB([]interface{}{}, 1000, generateValueArgs, executeBatch)
+ require.NoError(t, err)
+ })
+
+ t.Run("TestSingleBatch", func(t *testing.T) {
+ callCount := 0
+ executeBatch := func(valuePart string, args []any) error {
+ callCount++
+ require.Equal(t, 2, len(args)/2) // each item adds 2 args
+ return nil
+ }
+ err := batchProcessDB(payload[:2], 2, generateValueArgs, executeBatch)
+ require.NoError(t, err)
+ require.Equal(t, 1, callCount)
+ })
+
+ t.Run("TestMultipleBatches", func(t *testing.T) {
+ callCount := 0
+ executeBatch := func(valuePart string, args []any) error {
+ callCount++
+ require.Equal(t, 2/callCount, len(args)/2) // each item adds 2 args
+ return nil
+ }
+ err := batchProcessDB(payload, 2, generateValueArgs, executeBatch)
+ require.NoError(t, err)
+ require.Equal(t, 2, callCount)
+ })
+}
diff --git a/server/datastore/mysql/schema.sql b/server/datastore/mysql/schema.sql
index 1b60c5b32e..1d8726bbfb 100644
--- a/server/datastore/mysql/schema.sql
+++ b/server/datastore/mysql/schema.sql
@@ -334,6 +334,24 @@ CREATE TABLE `host_mdm_apple_bootstrap_packages` (
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `host_mdm_apple_declarations` (
+ `host_uuid` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `status` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+ `operation_type` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+ `detail` text COLLATE utf8mb4_unicode_ci,
+ `checksum` binary(16) NOT NULL,
+ `declaration_uuid` varchar(37) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
+ `declaration_identifier` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `declaration_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
+ PRIMARY KEY (`host_uuid`,`declaration_uuid`),
+ KEY `status` (`status`),
+ KEY `operation_type` (`operation_type`),
+ CONSTRAINT `host_mdm_apple_declarations_ibfk_1` FOREIGN KEY (`status`) REFERENCES `mdm_delivery_status` (`status`) ON UPDATE CASCADE,
+ CONSTRAINT `host_mdm_apple_declarations_ibfk_2` FOREIGN KEY (`operation_type`) REFERENCES `mdm_operation_types` (`operation_type`) ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `host_mdm_apple_profiles` (
`profile_identifier` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`host_uuid` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
@@ -661,6 +679,46 @@ CREATE TABLE `mdm_apple_configuration_profiles` (
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `mdm_apple_declaration_activation_references` (
+ `declaration_uuid` varchar(37) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
+ `reference` varchar(37) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
+ PRIMARY KEY (`declaration_uuid`,`reference`),
+ KEY `reference` (`reference`),
+ CONSTRAINT `mdm_apple_declaration_activation_references_ibfk_1` FOREIGN KEY (`declaration_uuid`) REFERENCES `mdm_apple_declarations` (`declaration_uuid`) ON UPDATE CASCADE,
+ CONSTRAINT `mdm_apple_declaration_activation_references_ibfk_2` FOREIGN KEY (`reference`) REFERENCES `mdm_apple_declarations` (`declaration_uuid`) ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `mdm_apple_declarations` (
+ `declaration_uuid` varchar(37) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
+ `team_id` int(10) unsigned NOT NULL DEFAULT '0',
+ `identifier` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `raw_json` json NOT NULL,
+ `checksum` binary(16) NOT NULL,
+ `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `uploaded_at` timestamp NULL DEFAULT NULL,
+ PRIMARY KEY (`declaration_uuid`),
+ UNIQUE KEY `idx_mdm_apple_declaration_team_identifier` (`team_id`,`identifier`),
+ UNIQUE KEY `idx_mdm_apple_declaration_team_name` (`team_id`,`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `mdm_apple_declarative_requests` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `enrollment_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `message_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `raw_json` text COLLATE utf8mb4_unicode_ci,
+ PRIMARY KEY (`id`),
+ KEY `mdm_apple_declarative_requests_enrollment_id` (`enrollment_id`),
+ CONSTRAINT `mdm_apple_declarative_requests_enrollment_id` FOREIGN KEY (`enrollment_id`) REFERENCES `nano_enrollments` (`id`) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `mdm_apple_default_setup_assistants` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`team_id` int(10) unsigned DEFAULT NULL,
@@ -738,6 +796,22 @@ CREATE TABLE `mdm_configuration_profile_labels` (
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `mdm_declaration_labels` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `apple_declaration_uuid` varchar(37) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
+ `label_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `label_id` int(10) unsigned DEFAULT NULL,
+ `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `uploaded_at` timestamp NULL DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `idx_mdm_declaration_labels_label_name` (`apple_declaration_uuid`,`label_name`),
+ KEY `label_id` (`label_id`),
+ CONSTRAINT `mdm_declaration_labels_ibfk_1` FOREIGN KEY (`apple_declaration_uuid`) REFERENCES `mdm_apple_declarations` (`declaration_uuid`) ON DELETE CASCADE,
+ CONSTRAINT `mdm_declaration_labels_ibfk_3` FOREIGN KEY (`label_id`) REFERENCES `labels` (`id`) ON DELETE SET NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `mdm_delivery_status` (
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`status`)
@@ -809,9 +883,9 @@ CREATE TABLE `migration_status_tables` (
`tstamp` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`)
-) ENGINE=InnoDB AUTO_INCREMENT=260 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+) ENGINE=InnoDB AUTO_INCREMENT=262 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
-INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01');
+INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01');
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `mobile_device_management_solutions` (
diff --git a/server/datastore/mysql/testing_utils.go b/server/datastore/mysql/testing_utils.go
index 6a329f7f97..a1eadba60d 100644
--- a/server/datastore/mysql/testing_utils.go
+++ b/server/datastore/mysql/testing_utils.go
@@ -313,11 +313,12 @@ func TruncateTables(t testing.TB, ds *Datastore, tables ...string) {
// be truncated - a more precise approach must be used for those, e.g.
// delete where id > max before test, or something like that.
nonEmptyTables := map[string]bool{
- "app_config_json": true,
- "migration_status_tables": true,
- "osquery_options": true,
- "mdm_delivery_status": true,
- "mdm_operation_types": true,
+ "app_config_json": true,
+ "migration_status_tables": true,
+ "osquery_options": true,
+ "mdm_delivery_status": true,
+ "mdm_operation_types": true,
+ "mdm_apple_declaration_categories": true,
}
ctx := context.Background()
diff --git a/server/fleet/activities.go b/server/fleet/activities.go
index 2fb7a4dbc0..abd4835a4d 100644
--- a/server/fleet/activities.go
+++ b/server/fleet/activities.go
@@ -84,6 +84,9 @@ var ActivityDetailsList = []ActivityDetails{
ActivityTypeLockedHost{},
ActivityTypeUnlockedHost{},
ActivityTypeWipedHost{},
+
+ ActivityTypeCreatedDeclarationProfile{},
+ ActivityTypeDeletedDeclarationProfile{},
}
type ActivityDetails interface {
@@ -1317,6 +1320,56 @@ func (a ActivityTypeWipedHost) Documentation() (activity, details, detailsExampl
}`
}
+type ActivityTypeCreatedDeclarationProfile struct {
+ ProfileName string `json:"profile_name"`
+ Identifier string `json:"identifier"`
+ TeamID *uint `json:"team_id"`
+ TeamName *string `json:"team_name"`
+}
+
+func (a ActivityTypeCreatedDeclarationProfile) ActivityName() string {
+ return "created_declaration_profile"
+}
+
+func (a ActivityTypeCreatedDeclarationProfile) Documentation() (activity string, details string, detailsExample string) {
+ return `Generated when a user adds a new macOS declaration to a team (or no team).`,
+ `This activity contains the following fields:
+- "profile_name": Name of the declaration.
+- "identifier": Identifier of the declaration.
+- "team_id": The ID of the team that the declaration applies to, ` + "`null`" + ` if it applies to devices that are not in a team.
+- "team_name": The name of the team that the declaration applies to, ` + "`null`" + ` if it applies to devices that are not in a team.`, `{
+ "profile_name": "Passcode requirements",
+ "profile_identifier": "com.my.declaration",
+ "team_id": 123,
+ "team_name": "Workstations"
+}`
+}
+
+type ActivityTypeDeletedDeclarationProfile struct {
+ ProfileName string `json:"profile_name"`
+ Identifier string `json:"identifier"`
+ TeamID *uint `json:"team_id"`
+ TeamName *string `json:"team_name"`
+}
+
+func (a ActivityTypeDeletedDeclarationProfile) ActivityName() string {
+ return "deleted_declaration_profile"
+}
+
+func (a ActivityTypeDeletedDeclarationProfile) Documentation() (activity string, details string, detailsExample string) {
+ return `Generated when a user removes a macOS declaration from a team (or no team).`,
+ `This activity contains the following fields:
+- "profile_name": Name of the declaration.
+- "identifier": Identifier of the declaration.
+- "team_id": The ID of the team that the declaration applies to, ` + "`null`" + ` if it applies to devices that are not in a team.
+- "team_name": The name of the team that the declaration applies to, ` + "`null`" + ` if it applies to devices that are not in a team.`, `{
+ "profile_name": "Passcode requirements",
+ "profile_identifier": "com.my.declaration",
+ "team_id": 123,
+ "team_name": "Workstations"
+}`
+}
+
// LogRoleChangeActivities logs activities for each role change, globally and one for each change in teams.
func LogRoleChangeActivities(ctx context.Context, ds Datastore, adminUser *User, oldGlobalRole *string, oldTeamRoles []UserTeam, user *User) error {
if user.GlobalRole != nil && (oldGlobalRole == nil || *oldGlobalRole != *user.GlobalRole) {
diff --git a/server/fleet/apple_mdm.go b/server/fleet/apple_mdm.go
index 0b074b87dc..22bd82d6d4 100644
--- a/server/fleet/apple_mdm.go
+++ b/server/fleet/apple_mdm.go
@@ -451,6 +451,8 @@ const (
DEPAssignProfileResponseFailed DEPAssignProfileResponseStatus = "FAILED"
)
+const MDMAppleDeclarationUUIDPrefix = "d"
+
// NanoEnrollment represents a row in the nano_enrollments table managed by
// nanomdm. It is meant to be used internally by the server, not to be returned
// as part of endpoints, and as a precaution its json-encoding is explicitly
@@ -533,3 +535,287 @@ type SCEPIdentityAssociation struct {
EnrollReference string `db:"enroll_reference"`
RenewCommandUUID string `db:"renew_command_uuid"`
}
+
+// MDMAppleDeclaration represents a DDM JSON declaration.
+type MDMAppleDeclaration struct {
+ // DeclarationUUID is the unique identifier of the declaration in
+ // Fleet. Since we use the same endpoints for declarations and profiles:
+ // - This is marshalled as profile_uuid
+ // - The value has a prefix (TODO: @jahzielv to determine and document this)
+ DeclarationUUID string `db:"declaration_uuid" json:"profile_uuid"`
+
+ // TeamID is the id of the team with which the declaration is associated. A nil team id
+ // represents a declaration that is not associated with any team.
+ TeamID *uint `db:"team_id" json:"team_id"`
+
+ // Identifier corresponds to the "Identifier" key of the associated declaration.
+ // Fleet requires that Identifier must be unique in combination with the Name and TeamID.
+ Identifier string `db:"identifier" json:"identifier"`
+
+ // Name corresponds to the file name of the associated JSON declaration payload.
+ // Fleet requires that Name must be unique in combination with the Identifier and TeamID.
+ Name string `db:"name" json:"name"`
+
+ // RawJSON is the raw JSON content of the declaration
+ RawJSON json.RawMessage `db:"raw_json" json:"-"`
+
+ // Checksum is a checksum of the JSON contents
+ Checksum string `db:"checksum" json:"-"`
+
+ // Labels are the labels associated with this Declaration
+ Labels []ConfigurationProfileLabel `db:"labels" json:"labels,omitempty"`
+
+ CreatedAt time.Time `db:"created_at" json:"created_at"`
+ UploadedAt time.Time `db:"uploaded_at" json:"uploaded_at"`
+}
+
+type MDMAppleRawDeclaration struct {
+ // Type is the "Type" field on the raw declaration JSON.
+ Type string `json:"Type"`
+ Identifier string `json:"Identifier"`
+}
+
+// ForbiddenDeclTypes is a set of declaration types that are not allowed to be
+// added by users into Fleet.
+var ForbiddenDeclTypes = map[string]struct{}{
+ "com.apple.configuration.account.caldav": {},
+ "com.apple.configuration.account.carddav": {},
+ "com.apple.configuration.account.exchange": {},
+ "com.apple.configuration.account.google": {},
+ "com.apple.configuration.account.ldap": {},
+ "com.apple.configuration.account.mail": {},
+ "com.apple.configuration.screensharing.connection": {},
+ "com.apple.configuration.security.certificate": {},
+ "com.apple.configuration.security.identity": {},
+ "com.apple.configuration.security.passkey.attestation": {},
+ "com.apple.configuration.services.configuration-files": {},
+ "com.apple.configuration.watch.enrollment": {},
+}
+
+func (r *MDMAppleRawDeclaration) ValidateUserProvided() error {
+ var err error
+
+ // Check against types we don't allow
+ if r.Type == `com.apple.configuration.softwareupdate.enforcement.specific` {
+ return NewInvalidArgumentError(r.Type, "Declaration profile can’t include OS updates settings. To control these settings, go to OS updates.")
+ }
+
+ if _, forbidden := ForbiddenDeclTypes[r.Type]; forbidden {
+ return NewInvalidArgumentError(r.Type, "Only configuration declarations that don’t require an asset reference are supported.")
+ }
+
+ if r.Type == "com.apple.configuration.management.status-subscriptions" {
+ return NewInvalidArgumentError(r.Type, "Declaration profile can’t include status subscription type. To get host’s vitals, please use queries and policies.")
+ }
+
+ if !strings.HasPrefix(r.Type, "com.apple.configuration") {
+ return NewInvalidArgumentError(r.Type, "Only configuration declarations (com.apple.configuration) are supported.")
+ }
+
+ return err
+}
+
+func GetRawDeclarationValues(raw []byte) (*MDMAppleRawDeclaration, error) {
+ var rawDecl MDMAppleRawDeclaration
+ if err := json.Unmarshal(raw, &rawDecl); err != nil {
+ return nil, NewInvalidArgumentError("declaration", fmt.Sprintf("Couldn't upload. The file should include valid JSON: %s", err)).WithStatus(http.StatusBadRequest)
+ }
+
+ return &rawDecl, nil
+}
+
+// MDMAppleHostDeclaration represents the state of a declaration on a host
+type MDMAppleHostDeclaration struct {
+ // HostUUID is the uuid of the host affected by this declaration
+ HostUUID string `db:"host_uuid" json:"-"`
+
+ // DeclarationUUID is the unique identifier of the declaration in
+ // Fleet. Since we use the same endpoints for declarations and profiles:
+ // - This is marshalled as profile_uuid
+ // - The value has a prefix (TODO: @jahzielv to determine and document this)
+ DeclarationUUID string `db:"declaration_uuid" json:"profile_uuid"`
+
+ // Name corresponds to the file name of the associated JSON declaration payload.
+ Name string `db:"declaration_name" json:"name"`
+
+ // Identifier corresponds to the "Identifier" key of the associated declaration.
+ Identifier string `db:"declaration_identifier" json:"-"`
+
+ // Status represent the current state of the declaration, as known by the Fleet server.
+ Status *MDMDeliveryStatus `db:"status" json:"status"`
+
+ // Operation type represents the operation being performed.
+ OperationType MDMOperationType `db:"operation_type" json:"operation_type"`
+
+ // Detail contains any messages that must be surfaced to the user,
+ // either by the MDM protocol or the Fleet server.
+ Detail string `db:"detail" json:"detail"`
+
+ // Checksum contains the MD5 checksum of the declaration JSON uploaded
+ // by the IT admin. Fleet uses this value as the ServerToken.
+ Checksum string `db:"checksum" json:"-"`
+}
+
+func NewMDMAppleDeclaration(raw []byte, teamID *uint, name string, declType, ident string) *MDMAppleDeclaration {
+ var decl MDMAppleDeclaration
+
+ decl.Identifier = ident
+ decl.Name = name
+ decl.RawJSON = raw
+ decl.TeamID = teamID
+
+ return &decl
+}
+
+// MDMAppleDDMTokensResponse is the response from the DDM tokens endpoint.
+//
+// https://developer.apple.com/documentation/devicemanagement/tokensresponse
+type MDMAppleDDMTokensResponse struct {
+ SyncTokens MDMAppleDDMDeclarationsToken
+}
+
+// MDMAppleDDMDeclarationsToken is dictionary describes the state of declarations on the server.
+//
+// https://developer.apple.com/documentation/devicemanagement/synchronizationtokens
+type MDMAppleDDMDeclarationsToken struct {
+ DeclarationsToken string `db:"checksum"`
+ Timestamp time.Time `db:"latest_created_timestamp"`
+}
+
+// MDMAppleDDMDeclarationItemsResponse is the response from the DDM declaration items endpoint.
+//
+// https://developer.apple.com/documentation/devicemanagement/declarationitemsresponse
+type MDMAppleDDMDeclarationItemsResponse struct {
+ Declarations MDMAppleDDMManifestItems
+ DeclarationsToken string
+}
+
+// MDMAppleDDMManifestItems is a dictionary that contains the lists of declarations available on the
+// server.
+//
+// https://developer.apple.com/documentation/devicemanagement/declarationitemsresponse/manifestdeclarationitems
+type MDMAppleDDMManifestItems struct {
+ Activations []MDMAppleDDMManifest
+ Assets []MDMAppleDDMManifest
+ Configurations []MDMAppleDDMManifest
+ Management []MDMAppleDDMManifest
+}
+
+// MDMAppleDDMManifest is a dictionary that describes a declaration.
+//
+// https://developer.apple.com/documentation/devicemanagement/declarationitemsresponse/manifestdeclarationitems
+type MDMAppleDDMManifest struct {
+ Identifier string
+ ServerToken string
+}
+
+// MDMAppleDDMDeclarationItem represents a declaration item in the datastore. It is used to
+// construct the DDM `declaration-items` endpoint response.
+//
+// https://developer.apple.com/documentation/devicemanagement/declarationitemsresponse
+type MDMAppleDDMDeclarationItem struct {
+ Identifier string `db:"identifier"`
+ ServerToken string `db:"checksum"`
+}
+
+// MDMAppleDDMDeclarationResponse represents a declaration in the datastore. It is used for the DDM
+// `declaration/.../...` enpoint response.
+//
+// https://developer.apple.com/documentation/devicemanagement/declarationresponse
+type MDMAppleDDMDeclarationResponse struct {
+ Identifier string `db:"identifier"`
+ Type string `db:"type"`
+ Payload json.RawMessage `db:"payload"`
+ ServerToken string `db:"server_token"`
+}
+
+// MDMAppleDDMStatusReport represents a report of the device's current state.
+//
+// https://developer.apple.com/documentation/devicemanagement/statusreport
+type MDMAppleDDMStatusReport struct {
+ StatusItems MDMAppleDDMStatusItems `json:"StatusItems"`
+ Errors []MDMAppleDDMErrors `json:"Errors"`
+}
+
+// MDMAppleDDMStatusItems are the status items for a report.
+//
+// https://developer.apple.com/documentation/devicemanagement/statusreport/statusitems
+type MDMAppleDDMStatusItems struct {
+ Management MDMAppleDDMStatusManagement `json:"management"`
+}
+
+// MDMAppleDDMStatusManagement represents status report of the client's
+// processed declarations.
+//
+// https://developer.apple.com/documentation/devicemanagement/statusmanagementdeclarations
+type MDMAppleDDMStatusManagement struct {
+ Declarations MDMAppleDDMStatusDeclarations `json:"declarations"`
+}
+
+// MDMAppleDDMStatusDeclarations represents a collection of the client's
+// processed declarations.
+//
+// https://developer.apple.com/documentation/devicemanagement/statusmanagementdeclarationsdeclarationsobject
+type MDMAppleDDMStatusDeclarations struct {
+ // Activations is an array of declarations that represent the client's
+ // processed activation types.
+ Activations []MDMAppleDDMStatusDeclaration `json:"activations"`
+ // Configurations is an array of declarations that represent the
+ // client's processed configuration types.
+ Configurations []MDMAppleDDMStatusDeclaration `json:"configurations"`
+ // Assets is an array of declarations that represent the client's
+ // processed assets.
+ Assets []MDMAppleDDMStatusDeclaration `json:"assets"`
+ // Management is an array of declarations that represent the client's
+ // processed declaration types.
+ Management []MDMAppleDDMStatusDeclaration `json:"management"`
+}
+
+type MDMAppleDeclarationValidity string
+
+const (
+ MDMAppleDeclarationValid MDMAppleDeclarationValidity = "valid"
+ MDMAppleDeclarationInvalid MDMAppleDeclarationValidity = "invalid"
+ MDMAppleDeclarationUnknown MDMAppleDeclarationValidity = "valid"
+)
+
+// MDMAppleDDMStatusDeclaration represents a processed declaration for the client.
+//
+// https://developer.apple.com/documentation/devicemanagement/statusmanagementdeclarationsdeclarationobject
+type MDMAppleDDMStatusDeclaration struct {
+ // Active signals if the declaration is active on the device.
+ Active bool `json:"active"`
+ // Identifier is the identifier of the declaration this status report refers to.
+ Identifier string `json:"identifier"`
+ // Valid defines the validity of the declaration. If it's invalid, the
+ // reasons property contains more details.
+ Valid MDMAppleDeclarationValidity `json:"valid"`
+ // ServerToken of the declaration this status report refers to.
+ ServerToken string `json:"server-token"`
+ // Reasons are the details of any client errors.
+ Reasons []MDMAppleDDMStatusErrorReason `json:"reasons,omitempty"`
+}
+
+// A status report's error that contains the status item and the reasons for
+// the error.
+//
+// https://developer.apple.com/documentation/devicemanagement/statusreport/error
+type MDMAppleDDMErrors struct {
+ // StatusItem is the status item that this error pertains to.
+ StatusItem string `json:"StatusItem"`
+ // Reasons is an array of reasons for the error.
+ Reasons []MDMAppleDDMStatusErrorReason `json:"Reasons"`
+}
+
+// A status report that contains details about an error.
+//
+// https://developer.apple.com/documentation/devicemanagement/statusreason
+type MDMAppleDDMStatusErrorReason struct {
+ // Code is the error code for this error.
+ Code string `json:"Code"`
+ // Description is a short error description.
+ Description string `json:"Description"`
+ // Details is a dictionary that contains further details about this
+ // error.
+ Details map[string]any `json:"Details"`
+}
diff --git a/server/fleet/datastore.go b/server/fleet/datastore.go
index 827ba0be36..99ba5b7f0a 100644
--- a/server/fleet/datastore.go
+++ b/server/fleet/datastore.go
@@ -927,6 +927,9 @@ type Datastore interface {
// profile uuid.
GetMDMAppleConfigProfile(ctx context.Context, profileUUID string) (*MDMAppleConfigProfile, error)
+ // GetMDMAppleDeclaration returns the declaration corresponding to the specified uuid.
+ GetMDMAppleDeclaration(ctx context.Context, declUUID string) (*MDMAppleDeclaration, error)
+
// ListMDMAppleConfigProfiles lists mdm config profiles associated with the specified team id.
// For global config profiles, specify nil as the team id.
ListMDMAppleConfigProfiles(ctx context.Context, teamID *uint) ([]*MDMAppleConfigProfile, error)
@@ -1169,6 +1172,29 @@ type Datastore interface {
// serials.
UpdateDEPAssignProfileRetryPending(ctx context.Context, jobID uint, serials []string) error
+ // InsertMDMAppleDDMRequest inserts a DDM request.
+ InsertMDMAppleDDMRequest(ctx context.Context, hostUUID, messageType, rawJSON string) error
+
+ // MDMAppleDDMDeclarationsToken returns the token used to synchronize declarations for the
+ // specified host UUID.
+ MDMAppleDDMDeclarationsToken(ctx context.Context, hostUUID string) (*MDMAppleDDMDeclarationsToken, error)
+ // MDMAppleDDMDeclarationItems returns the declaration items for the specified host UUID.
+ MDMAppleDDMDeclarationItems(ctx context.Context, hostUUID string) ([]MDMAppleDDMDeclarationItem, error)
+ // MDMAppleDDMDeclarationPayload returns the declaration payload for the specified identifier and team.
+ MDMAppleDDMDeclarationsResponse(ctx context.Context, identifier string, hostUUID string) (*MDMAppleDeclaration, error)
+ //MDMAppleBatchSetHostDeclarationState
+ MDMAppleBatchSetHostDeclarationState(ctx context.Context) ([]string, error)
+ // MDMAppleStoreDDMStatusReport receives a host.uuid and a slice
+ // of declarations, and updates the tracked host declaration status for
+ // matching declarations.
+ //
+ // It also takes care of cleaning up all host declarations that are
+ // pending removal.
+ MDMAppleStoreDDMStatusReport(ctx context.Context, hostUUID string, updates []*MDMAppleHostDeclaration) error
+ // MDMAppleSetDeclarationsAsVerifying updates all
+ // ("pending", "install") declarations for a host to be ("verifying", "install")
+ MDMAppleSetDeclarationsAsVerifying(ctx context.Context, hostUUID string) error
+
///////////////////////////////////////////////////////////////////////////////
// Microsoft MDM
@@ -1287,7 +1313,10 @@ type Datastore interface {
// BatchSetMDMProfiles sets the MDM Apple or Windows profiles for the given team or
// no team in a single transaction.
- BatchSetMDMProfiles(ctx context.Context, tmID *uint, macProfiles []*MDMAppleConfigProfile, winProfiles []*MDMWindowsConfigProfile) error
+ BatchSetMDMProfiles(ctx context.Context, tmID *uint, macProfiles []*MDMAppleConfigProfile, winProfiles []*MDMWindowsConfigProfile, macDeclarations []*MDMAppleDeclaration) error
+
+ // NewMDMAppleDeclaration creates and returns a new MDM Apple declaration.
+ NewMDMAppleDeclaration(ctx context.Context, declaration *MDMAppleDeclaration) (*MDMAppleDeclaration, error)
///////////////////////////////////////////////////////////////////////////////
// Host Script Results
diff --git a/server/fleet/mdm.go b/server/fleet/mdm.go
index dbb3808aab..baa501bb4b 100644
--- a/server/fleet/mdm.go
+++ b/server/fleet/mdm.go
@@ -413,6 +413,24 @@ func NewMDMConfigProfilePayloadFromApple(cp *MDMAppleConfigProfile) *MDMConfigPr
}
}
+func NewMDMConfigProfilePayloadFromAppleDDM(decl *MDMAppleDeclaration) *MDMConfigProfilePayload {
+ var tid *uint
+ if decl.TeamID != nil && *decl.TeamID > 0 {
+ tid = decl.TeamID
+ }
+ return &MDMConfigProfilePayload{
+ ProfileUUID: decl.DeclarationUUID,
+ TeamID: tid,
+ Name: decl.Name,
+ Identifier: decl.Identifier,
+ Platform: "darwin",
+ Checksum: []byte(decl.Checksum),
+ CreatedAt: decl.CreatedAt,
+ UploadedAt: decl.UploadedAt,
+ Labels: decl.Labels,
+ }
+}
+
// MDMProfileSpec represents the spec used to define configuration
// profiles via yaml files.
type MDMProfileSpec struct {
diff --git a/server/fleet/service.go b/server/fleet/service.go
index 2fef5eeec5..c38c9550c5 100644
--- a/server/fleet/service.go
+++ b/server/fleet/service.go
@@ -652,18 +652,29 @@ type Service interface {
// NewMDMAppleConfigProfile creates a new configuration profile for the specified team.
NewMDMAppleConfigProfile(ctx context.Context, teamID uint, r io.Reader, labels []string) (*MDMAppleConfigProfile, error)
+ // NewMDMAppleConfigProfileWithPayload creates a new declaration for the specified team.
+ NewMDMAppleDeclaration(ctx context.Context, teamID uint, r io.Reader, labels []string, name string) (*MDMAppleDeclaration, error)
+
// GetMDMAppleConfigProfileByDeprecatedID retrieves the specified Apple
// configuration profile via its numeric ID. This method is deprecated and
// should not be used for new endpoints.
GetMDMAppleConfigProfileByDeprecatedID(ctx context.Context, profileID uint) (*MDMAppleConfigProfile, error)
// GetMDMAppleConfigProfile retrieves the specified configuration profile.
GetMDMAppleConfigProfile(ctx context.Context, profileUUID string) (*MDMAppleConfigProfile, error)
+
+ // GetMDMAppleDeclaration retrieves the specified declaration.
+ GetMDMAppleDeclaration(ctx context.Context, declarationUUID string) (*MDMAppleDeclaration, error)
+
// DeleteMDMAppleConfigProfileByDeprecatedID deletes the specified Apple
// configuration profile via its numeric ID. This method is deprecated and
// should not be used for new endpoints.
DeleteMDMAppleConfigProfileByDeprecatedID(ctx context.Context, profileID uint) error
// DeleteMDMAppleConfigProfile deletes the specified configuration profile.
DeleteMDMAppleConfigProfile(ctx context.Context, profileUUID string) error
+
+ // DeleteMDMAppleDeclaration deletes the specified declaration.
+ DeleteMDMAppleDeclaration(ctx context.Context, declarationUUID string) error
+
// ListMDMAppleConfigProfiles returns the list of all the configuration profiles for the
// specified team.
ListMDMAppleConfigProfiles(ctx context.Context, teamID uint) ([]*MDMAppleConfigProfile, error)
diff --git a/server/mdm/apple/commander.go b/server/mdm/apple/commander.go
index 24f8def567..33d17daa78 100644
--- a/server/mdm/apple/commander.go
+++ b/server/mdm/apple/commander.go
@@ -226,6 +226,28 @@ func (svc *MDMAppleCommander) AccountConfiguration(ctx context.Context, hostUUID
return svc.EnqueueCommand(ctx, hostUUIDs, raw)
}
+// DeclarativeManagement sends the homonym [command][1] to the device to enable DDM or start a new DDM session.
+//
+// [1]: https://developer.apple.com/documentation/devicemanagement/declarativemanagementcommand
+func (svc *MDMAppleCommander) DeclarativeManagement(ctx context.Context, hostUUIDs []string, uuid string) error {
+ raw := fmt.Sprintf(`
+
+
+
+ Command
+
+ RequestType
+ DeclarativeManagement
+
+
+ CommandUUID
+ %s
+
+ `, uuid)
+
+ return svc.EnqueueCommand(ctx, hostUUIDs, raw)
+}
+
func (svc *MDMAppleCommander) DeviceConfigured(ctx context.Context, hostUUID, cmdUUID string) error {
raw := fmt.Sprintf(`
diff --git a/server/mdm/apple/util.go b/server/mdm/apple/util.go
index 801ca37d67..bc8b2bc6e4 100644
--- a/server/mdm/apple/util.go
+++ b/server/mdm/apple/util.go
@@ -101,6 +101,7 @@ func GenerateRandomPin(length int) string {
return fmt.Sprintf(f, v)
}
+// FmtErrorChain formats Command error message for macOS MDM v1
func FmtErrorChain(chain []mdm.ErrorChain) string {
var sb strings.Builder
for _, mdmErr := range chain {
@@ -113,6 +114,15 @@ func FmtErrorChain(chain []mdm.ErrorChain) string {
return sb.String()
}
+// FmtDDMError formats a DDM error message
+func FmtDDMError(reasons []fleet.MDMAppleDDMStatusErrorReason) string {
+ var errMsg strings.Builder
+ for _, r := range reasons {
+ errMsg.WriteString(fmt.Sprintf("%s: %s %+v\n", r.Code, r.Description, r.Details))
+ }
+ return errMsg.String()
+}
+
func EnrollURL(token string, appConfig *fleet.AppConfig) (string, error) {
enrollURL, err := url.Parse(appConfig.ServerSettings.ServerURL)
if err != nil {
diff --git a/server/mdm/mdm.go b/server/mdm/mdm.go
index 00e515667b..5a15dd8c00 100644
--- a/server/mdm/mdm.go
+++ b/server/mdm/mdm.go
@@ -50,7 +50,7 @@ func GetRawProfilePlatform(profile []byte) string {
bytes.EqualFold(prefix, trimmedProfile[:len(prefix)])
}
- if prefixMatches([]byte("= 1 {
+ tm, err := svc.EnterpriseOverrides.TeamByIDOrName(ctx, &teamID, nil)
+ if err != nil {
+ return nil, ctxerr.Wrap(ctx, err)
+ }
+ teamName = tm.Name
+ }
+
+ data, err := io.ReadAll(r)
+ if err != nil {
+ return nil, err
+ }
+
+ var tmID *uint
+ if teamID >= 1 {
+ tmID = &teamID
+ }
+
+ validatedLabels, err := svc.validateDeclarationLabels(ctx, labels)
+ if err != nil {
+ return nil, err
+ }
+
+ // TODO(roberto): Maybe GetRawDeclarationValues belongs inside NewMDMAppleDeclaration? We can refactor this in a follow up.
+ rawDecl, err := fleet.GetRawDeclarationValues(data)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := rawDecl.ValidateUserProvided(); err != nil {
+ return nil, err
+ }
+
+ d := fleet.NewMDMAppleDeclaration(data, tmID, name, rawDecl.Type, rawDecl.Identifier)
+
+ // TODO(roberto): Is this already handled in NewMDMAppleDeclaration? Could we add the labels as well?
+ d.Labels = validatedLabels
+ d.TeamID = tmID
+
+ decl, err := svc.ds.NewMDMAppleDeclaration(ctx, d)
+ if err != nil {
+ return nil, err
+ }
+
+ var (
+ actTeamID *uint
+ actTeamName *string
+ )
+ if teamID > 0 {
+ actTeamID = &teamID
+ actTeamName = &teamName
+ }
+ if err := svc.ds.NewActivity(ctx, authz.UserFromContext(ctx), &fleet.ActivityTypeCreatedDeclarationProfile{
+ TeamID: actTeamID,
+ TeamName: actTeamName,
+ ProfileName: decl.Name,
+ Identifier: decl.Identifier,
+ }); err != nil {
+ return nil, ctxerr.Wrap(ctx, err, "logging activity for create mdm apple declaration")
+ }
+
+ return decl, nil
+}
+
+func (svc *Service) batchValidateDeclarationLabels(ctx context.Context, labelNames []string) (map[string]fleet.ConfigurationProfileLabel, error) {
+ if len(labelNames) == 0 {
+ return nil, nil
+ }
+
+ labels, err := svc.ds.LabelIDsByName(ctx, labelNames)
+ if err != nil {
+ return nil, ctxerr.Wrap(ctx, err, "getting label IDs by name")
+ }
+
+ uniqueNames := make(map[string]bool)
+ for _, entry := range labelNames {
+ if _, value := uniqueNames[entry]; !value {
+ uniqueNames[entry] = true
+ }
+ }
+
+ if len(labels) != len(uniqueNames) {
+ return nil, &fleet.BadRequestError{
+ Message: "some or all the labels provided don't exist",
+ InternalErr: fmt.Errorf("names provided: %v", labelNames),
+ }
+ }
+
+ profLabels := make(map[string]fleet.ConfigurationProfileLabel)
+ for labelName, labelID := range labels {
+ profLabels[labelName] = fleet.ConfigurationProfileLabel{
+ LabelName: labelName,
+ LabelID: labelID,
+ }
+ }
+ return profLabels, nil
+}
+
+func (svc *Service) validateDeclarationLabels(ctx context.Context, labelNames []string) ([]fleet.ConfigurationProfileLabel, error) {
+ labelMap, err := svc.batchValidateDeclarationLabels(ctx, labelNames)
+ if err != nil {
+ return nil, ctxerr.Wrap(ctx, err, "validating declaration labels")
+ }
+
+ var declLabels []fleet.ConfigurationProfileLabel
+ for _, label := range labelMap {
+ declLabels = append(declLabels, label)
+ }
+ return declLabels, nil
+}
+
type listMDMAppleConfigProfilesRequest struct {
TeamID uint `query:"team_id,optional"`
}
@@ -515,6 +646,25 @@ func (svc *Service) GetMDMAppleConfigProfile(ctx context.Context, profileUUID st
return cp, nil
}
+func (svc *Service) GetMDMAppleDeclaration(ctx context.Context, profileUUID string) (*fleet.MDMAppleDeclaration, error) {
+ // first we perform a perform basic authz check
+ if err := svc.authz.Authorize(ctx, &fleet.Team{}, fleet.ActionRead); err != nil {
+ return nil, err
+ }
+
+ cp, err := svc.ds.GetMDMAppleDeclaration(ctx, profileUUID)
+ if err != nil {
+ return nil, ctxerr.Wrap(ctx, err)
+ }
+
+ // now we can do a specific authz check based on team id of profile before we return the profile
+ if err := svc.authz.Authorize(ctx, &fleet.MDMConfigProfileAuthz{TeamID: cp.TeamID}, fleet.ActionRead); err != nil {
+ return nil, err
+ }
+
+ return cp, nil
+}
+
type deleteMDMAppleConfigProfileRequest struct {
ProfileID uint `url:"profile_id"`
}
@@ -623,6 +773,85 @@ func (svc *Service) DeleteMDMAppleConfigProfile(ctx context.Context, profileUUID
return nil
}
+func (svc *Service) DeleteMDMAppleDeclaration(ctx context.Context, declUUID string) error {
+ // first we perform a perform basic authz check
+ if err := svc.authz.Authorize(ctx, &fleet.Team{}, fleet.ActionRead); err != nil {
+ return ctxerr.Wrap(ctx, err)
+ }
+
+ // check that Apple MDM is enabled - the middleware of that endpoint checks
+ // only that any MDM is enabled, maybe it's just Windows
+ if err := svc.VerifyMDMAppleConfigured(ctx); err != nil {
+ err := fleet.NewInvalidArgumentError("profile_uuid", fleet.AppleMDMNotConfiguredMessage).WithStatus(http.StatusBadRequest)
+ return ctxerr.Wrap(ctx, err, "check macOS MDM enabled")
+ }
+
+ decl, err := svc.ds.GetMDMAppleDeclaration(ctx, declUUID)
+ if err != nil {
+ return ctxerr.Wrap(ctx, err)
+ }
+
+ if _, ok := mdm_types.FleetReservedProfileNames()[decl.Name]; ok {
+ return &fleet.BadRequestError{
+ Message: "profiles managed by Fleet can't be deleted using this endpoint.",
+ InternalErr: fmt.Errorf("deleting profile %s is not allowed because it's managed by Fleet", decl.Name),
+ }
+ }
+
+ // TODO: refine our approach to deleting restricted/forbidden types of declarations so that we
+ // can check that Fleet-managed aren't being deleted; this can be addressed once we add support
+ // for more types of declarations
+ var d fleet.MDMAppleRawDeclaration
+ if err := json.Unmarshal(decl.RawJSON, &d); err != nil {
+ return ctxerr.Wrap(ctx, err, "unmarshalling declaration")
+ }
+ if err := d.ValidateUserProvided(); err != nil {
+ return ctxerr.Wrap(ctx, &fleet.BadRequestError{Message: err.Error()})
+ }
+
+ var teamName string
+ teamID := *decl.TeamID
+ if teamID >= 1 {
+ tm, err := svc.EnterpriseOverrides.TeamByIDOrName(ctx, &teamID, nil)
+ if err != nil {
+ return ctxerr.Wrap(ctx, err)
+ }
+ teamName = tm.Name
+ }
+
+ // now we can do a specific authz check based on team id of profile before we delete the profile
+ if err := svc.authz.Authorize(ctx, &fleet.MDMConfigProfileAuthz{TeamID: decl.TeamID}, fleet.ActionWrite); err != nil {
+ return ctxerr.Wrap(ctx, err)
+ }
+
+ if err := svc.ds.DeleteMDMAppleConfigProfile(ctx, declUUID); err != nil {
+ return ctxerr.Wrap(ctx, err)
+ }
+
+ if err := svc.ds.BulkSetPendingMDMHostProfiles(ctx, nil, []uint{teamID}, nil, nil); err != nil {
+ return ctxerr.Wrap(ctx, err, "bulk set pending host profiles")
+ }
+
+ var (
+ actTeamID *uint
+ actTeamName *string
+ )
+ if teamID > 0 {
+ actTeamID = &teamID
+ actTeamName = &teamName
+ }
+ if err := svc.ds.NewActivity(ctx, authz.UserFromContext(ctx), &fleet.ActivityTypeDeletedDeclarationProfile{
+ TeamID: actTeamID,
+ TeamName: actTeamName,
+ ProfileName: decl.Name,
+ Identifier: decl.Identifier,
+ }); err != nil {
+ return ctxerr.Wrap(ctx, err, "logging activity for delete mdm apple declaration")
+ }
+
+ return nil
+}
+
type getMDMAppleFileVaultSummaryRequest struct {
TeamID *uint `query:"team_id,optional"`
}
@@ -2225,7 +2454,6 @@ func (svc *MDMAppleCheckinAndCommandService) Authenticate(r *mdm.Request, m *mdm
InstalledFromDEP: info.DEPAssignedToFleet,
MDMPlatform: fleet.MDMPlatformApple,
})
-
}
// TokenUpdate handles MDM [TokenUpdate][1] requests.
@@ -2354,7 +2582,8 @@ func (svc *MDMAppleCheckinAndCommandService) UserAuthenticate(*mdm.Request, *mdm
// This method is executed after the request has been handled by nanomdm.
//
// [1]: https://developer.apple.com/documentation/devicemanagement/declarative_management_checkin
-func (svc *MDMAppleCheckinAndCommandService) DeclarativeManagement(*mdm.Request, *mdm.DeclarativeManagement) ([]byte, error) {
+func (svc *MDMAppleCheckinAndCommandService) DeclarativeManagement(r *mdm.Request, dm *mdm.DeclarativeManagement) ([]byte, error) {
+ // DeclarativeManagement is handled by the MDMAppleDDMService.
return nil, nil
}
@@ -2409,7 +2638,13 @@ func (svc *MDMAppleCheckinAndCommandService) CommandAndReportResults(r *mdm.Requ
cmdResult.Status == fleet.MDMAppleStatusCommandFormatError {
return nil, svc.ds.UpdateHostLockWipeStatusFromAppleMDMResult(r.Context, cmdResult.UDID, cmdResult.CommandUUID, requestType, cmdResult.Status == fleet.MDMAppleStatusAcknowledged)
}
+ case "DeclarativeManagement":
+ // set "pending-install" profiles to "verifying"
+ err := svc.ds.MDMAppleSetDeclarationsAsVerifying(r.Context, cmdResult.UDID)
+ return nil, ctxerr.Wrap(r.Context, err, "update declaration status on DeclarativeManagement ack")
+
}
+
return nil, nil
}
@@ -2504,6 +2739,34 @@ func ensureFleetdConfig(ctx context.Context, ds fleet.Datastore, logger kitlog.L
return nil
}
+func ReconcileAppleDeclarations(
+ ctx context.Context,
+ ds fleet.Datastore,
+ commander *apple_mdm.MDMAppleCommander,
+ logger kitlog.Logger,
+) error {
+
+ // batch set declarations as pending
+ changedHosts, err := ds.MDMAppleBatchSetHostDeclarationState(ctx)
+ if err != nil {
+ return ctxerr.Wrap(ctx, err, "updating host declaration state")
+ }
+
+ if len(changedHosts) == 0 {
+ logger.Log("msg", "no hosts with changed declarations")
+ return nil
+ }
+
+ // send a DeclarativeManagement command to start a sync
+ if err := commander.DeclarativeManagement(ctx, changedHosts, uuid.NewString()); err != nil {
+ return ctxerr.Wrap(ctx, err, "issuing DeclarativeManagement command")
+ }
+
+ logger.Log("msg", "sent DeclarativeManagement command", "host_number", len(changedHosts))
+
+ return nil
+}
+
func ReconcileAppleProfiles(
ctx context.Context,
ds fleet.Datastore,
@@ -2946,3 +3209,231 @@ func RenewSCEPCertificates(
return nil
}
+
+// MDMAppleDDMService is the service that handles MDM [DeclarativeManagement][1] requests.
+//
+// [1]: https://developer.apple.com/documentation/devicemanagement/declarative_management_checkin
+type MDMAppleDDMService struct {
+ ds fleet.Datastore
+ logger kitlog.Logger
+}
+
+func NewMDMAppleDDMService(ds fleet.Datastore, logger kitlog.Logger) *MDMAppleDDMService {
+ return &MDMAppleDDMService{
+ ds: ds,
+ logger: logger,
+ }
+}
+
+// DeclarativeManagement handles MDM [DeclarativeManagement][1] requests.
+//
+// This method is when the request has been handled by nanomdm.
+//
+// [1]: https://developer.apple.com/documentation/devicemanagement/declarative_management_checkin
+func (svc *MDMAppleDDMService) DeclarativeManagement(r *mdm.Request, dm *mdm.DeclarativeManagement) ([]byte, error) {
+ if dm == nil {
+ level.Debug(svc.logger).Log("msg", "ddm request received with nil payload")
+ return nil, nil
+ }
+ level.Debug(svc.logger).Log("msg", "ddm request received", "endpoint", dm.Endpoint)
+
+ if err := svc.ds.InsertMDMAppleDDMRequest(r.Context, dm.UDID, dm.Endpoint, string(dm.Data)); err != nil {
+ return nil, ctxerr.Wrap(r.Context, err, "insert ddm request history")
+ }
+
+ if dm.UDID == "" {
+ return nil, nano_service.NewHTTPStatusError(http.StatusBadRequest, ctxerr.New(r.Context, "missing UDID in request"))
+ }
+
+ switch {
+ case dm.Endpoint == "tokens":
+ level.Debug(svc.logger).Log("msg", "received tokens request")
+ return svc.handleTokens(r.Context, dm.UDID)
+
+ case dm.Endpoint == "declaration-items":
+ level.Debug(svc.logger).Log("msg", "received declaration-items request")
+ return svc.handleDeclarationItems(r.Context, dm.UDID)
+
+ case dm.Endpoint == "status":
+ level.Debug(svc.logger).Log("msg", "received status request")
+ return nil, svc.handleDeclarationStatus(r.Context, dm)
+
+ case strings.HasPrefix(dm.Endpoint, "declaration/"):
+ level.Debug(svc.logger).Log("msg", "received declarations request")
+ return svc.handleDeclarationsResponse(r.Context, dm.Endpoint, dm.UDID)
+
+ default:
+ return nil, nano_service.NewHTTPStatusError(http.StatusBadRequest, ctxerr.New(r.Context, fmt.Sprintf("unrecognized declarations endpoint: %s", dm.Endpoint)))
+ }
+}
+
+func (svc *MDMAppleDDMService) handleTokens(ctx context.Context, hostUUID string) ([]byte, error) {
+ tok, err := svc.ds.MDMAppleDDMDeclarationsToken(ctx, hostUUID)
+ if err != nil {
+ return nil, ctxerr.Wrap(ctx, err, "getting synchronization tokens")
+ }
+
+ b, err := json.Marshal(fleet.MDMAppleDDMTokensResponse{
+ SyncTokens: *tok,
+ })
+ if err != nil {
+ return nil, ctxerr.Wrap(ctx, err, "marshaling synchronization tokens")
+ }
+
+ return b, nil
+}
+
+func (svc *MDMAppleDDMService) handleDeclarationItems(ctx context.Context, hostUUID string) ([]byte, error) {
+ di, err := svc.ds.MDMAppleDDMDeclarationItems(ctx, hostUUID)
+ if err != nil {
+ return nil, ctxerr.Wrap(ctx, err, "getting synchronization tokens")
+ }
+
+ activations := []fleet.MDMAppleDDMManifest{}
+ configurations := []fleet.MDMAppleDDMManifest{}
+ for _, d := range di {
+ configurations = append(configurations, fleet.MDMAppleDDMManifest(d))
+ activations = append(activations, fleet.MDMAppleDDMManifest{
+ Identifier: fmt.Sprintf("%s.activation", d.Identifier),
+ ServerToken: d.ServerToken,
+ })
+ }
+
+ // TODO: Look for ways to optimize the declaration item query so that we don't have to get the declarations token separately.
+ dTok, err := svc.ds.MDMAppleDDMDeclarationsToken(ctx, hostUUID)
+ if err != nil {
+ return nil, ctxerr.Wrap(ctx, err, "getting declarations token")
+ }
+
+ b, err := json.Marshal(fleet.MDMAppleDDMDeclarationItemsResponse{
+ Declarations: fleet.MDMAppleDDMManifestItems{
+ Activations: activations,
+ Configurations: configurations,
+ Assets: []fleet.MDMAppleDDMManifest{},
+ Management: []fleet.MDMAppleDDMManifest{},
+ },
+ DeclarationsToken: dTok.DeclarationsToken,
+ })
+ if err != nil {
+ return nil, ctxerr.Wrap(ctx, err, "marshaling synchronization tokens")
+ }
+
+ return b, nil
+}
+
+func (svc *MDMAppleDDMService) handleDeclarationsResponse(ctx context.Context, endpoint string, hostUUID string) ([]byte, error) {
+ parts := strings.Split(endpoint, "/")
+ if len(parts) != 3 {
+ return nil, nano_service.NewHTTPStatusError(http.StatusBadRequest, ctxerr.New(ctx, fmt.Sprintf("unrecognized declarations endpoint: %s", endpoint)))
+ }
+ level.Debug(svc.logger).Log("msg", "parsed declarations request", "type", parts[1], "identifier", parts[2])
+
+ switch parts[1] {
+ case "activation":
+ return svc.handleActivationDeclaration(ctx, parts, hostUUID)
+ case "configuration":
+ return svc.handleConfigurationDeclaration(ctx, parts, hostUUID)
+ default:
+ return nil, newNotFoundError()
+ }
+}
+
+func (svc *MDMAppleDDMService) handleActivationDeclaration(ctx context.Context, parts []string, hostUUID string) ([]byte, error) {
+ references := strings.TrimSuffix(parts[2], ".activation")
+
+ // ensure the declaration for the requested activation stil exists
+ d, err := svc.ds.MDMAppleDDMDeclarationsResponse(ctx, references, hostUUID)
+ if err != nil {
+ if fleet.IsNotFound(err) {
+ return nil, nano_service.NewHTTPStatusError(http.StatusNotFound, err)
+ }
+ return nil, ctxerr.Wrap(ctx, err, "getting linked configuration for activation declaration")
+ }
+
+ response := fmt.Sprintf(`
+{
+ "Identifier": "%s",
+ "Payload": {
+ "StandardConfigurations": ["%s"]
+ },
+ "ServerToken": "%s",
+ "Type": "com.apple.activation.simple"
+}`, parts[2], references, d.Checksum)
+
+ return []byte(response), nil
+}
+
+func (svc *MDMAppleDDMService) handleConfigurationDeclaration(ctx context.Context, parts []string, hostUUID string) ([]byte, error) {
+ d, err := svc.ds.MDMAppleDDMDeclarationsResponse(ctx, parts[2], hostUUID)
+ if err != nil {
+ if fleet.IsNotFound(err) {
+ return nil, nano_service.NewHTTPStatusError(http.StatusNotFound, err)
+ }
+ return nil, ctxerr.Wrap(ctx, err, "getting declaration response")
+ }
+
+ var tempd map[string]any
+ if err := json.Unmarshal(d.RawJSON, &tempd); err != nil {
+ return nil, ctxerr.Wrap(ctx, err, "unmarshaling stored declaration")
+ }
+ tempd["ServerToken"] = d.Checksum
+
+ b, err := json.Marshal(tempd)
+ if err != nil {
+ return nil, ctxerr.Wrap(ctx, err, "marshaling declaration")
+ }
+ return b, nil
+}
+
+func (svc *MDMAppleDDMService) handleDeclarationStatus(ctx context.Context, dm *mdm.DeclarativeManagement) error {
+ var status fleet.MDMAppleDDMStatusReport
+ if err := json.Unmarshal(dm.Data, &status); err != nil {
+ return ctxerr.Wrap(ctx, err, "unmarshalling response")
+ }
+
+ configurationReports := status.StatusItems.Management.Declarations.Configurations
+ updates := make([]*fleet.MDMAppleHostDeclaration, len(configurationReports))
+ for i, r := range configurationReports {
+ var status fleet.MDMDeliveryStatus
+ var detail string
+ switch {
+ case r.Active && r.Valid == fleet.MDMAppleDeclarationValid:
+ status = fleet.MDMDeliveryVerified
+ case r.Valid == fleet.MDMAppleDeclarationInvalid:
+ status = fleet.MDMDeliveryFailed
+ detail = apple_mdm.FmtDDMError(r.Reasons)
+ default:
+ status = fleet.MDMDeliveryVerifying
+ }
+
+ updates[i] = &fleet.MDMAppleHostDeclaration{
+ Status: &status,
+ OperationType: fleet.MDMOperationTypeInstall,
+ Detail: detail,
+ Checksum: r.ServerToken,
+ }
+ }
+
+ if len(updates) == 0 {
+ return nil
+ }
+
+ // MDMAppleStoreDDMStatusReport takes care of cleaning ("pending", "remove")
+ // pairs for the host.
+ //
+ // TODO(roberto): in the DDM documentation, it's mentioned that status
+ // report will give you a "remove" status so the server can track
+ // removals. In my testing, I never saw this (after spending
+ // considerable time trying to make it work.)
+ //
+ // My current guess is that the documentation is implicitly referring
+ // to asset declarations (which deliver tangible "assets" to the host)
+ //
+ // The best indication I found so far, is that if the declaration is
+ // not in the report, then it's implicitly removed.
+ if err := svc.ds.MDMAppleStoreDDMStatusReport(ctx, dm.UDID, updates); err != nil {
+ return ctxerr.Wrap(ctx, err, "updating host declaration status with reports")
+ }
+
+ return nil
+}
diff --git a/server/service/apple_mdm_test.go b/server/service/apple_mdm_test.go
index bd72cbd822..764f4c50ac 100644
--- a/server/service/apple_mdm_test.go
+++ b/server/service/apple_mdm_test.go
@@ -2727,6 +2727,19 @@ func mobileconfigForTest(name, identifier string) []byte {
`, name, identifier, uuid.New().String()))
}
+func declBytesForTest(identifier string, payloadContent string) []byte {
+ tmpl := `{
+ "Type": "com.apple.configuration.decl%s",
+ "Identifier": "com.fleet.config%s",
+ "Payload": {
+ "ServiceType": "com.apple.service%s"
+ }
+ }`
+
+ declBytes := []byte(fmt.Sprintf(tmpl, identifier, identifier, payloadContent))
+ return declBytes
+}
+
func mobileconfigForTestWithContent(outerName, outerIdentifier, innerIdentifier, innerType, innerName string) []byte {
if innerName == "" {
innerName = outerName + ".inner"
diff --git a/server/service/client.go b/server/service/client.go
index 8384e3e54d..2bb5042235 100644
--- a/server/service/client.go
+++ b/server/service/client.go
@@ -294,8 +294,9 @@ func getProfilesContents(baseDir string, profiles []fleet.MDMProfileSpec) ([]fle
}
// by default, use the file name. macOS profiles use their PayloadDisplayName
- name := strings.TrimSuffix(filepath.Base(filePath), filepath.Ext(filePath))
- if mdm.GetRawProfilePlatform(fileContents) == "darwin" {
+ ext := filepath.Ext(filePath)
+ name := strings.TrimSuffix(filepath.Base(filePath), ext)
+ if mdm.GetRawProfilePlatform(fileContents) == "darwin" && ext == ".mobileconfig" {
mc, err := fleet.NewMDMAppleConfigProfile(fileContents, nil)
if err != nil {
return nil, fmt.Errorf("applying fleet config: %w", err)
diff --git a/server/service/handler.go b/server/service/handler.go
index db3208a537..a67cf5cc8f 100644
--- a/server/service/handler.go
+++ b/server/service/handler.go
@@ -1003,6 +1003,7 @@ func RegisterAppleMDMProtocolServices(
scepStorage scep_depot.Depot,
logger kitlog.Logger,
checkinAndCommandService nanomdm_service.CheckinAndCommandService,
+ ddmService nanomdm_service.DeclarativeManagement,
) error {
scepCACerts, scepCAKey, err := scepStorage.CA([]byte{})
if err != nil {
@@ -1011,7 +1012,7 @@ func RegisterAppleMDMProtocolServices(
if err := registerSCEP(mux, scepConfig, scepCACerts[0], scepCAKey, scepStorage, logger); err != nil {
return fmt.Errorf("scep: %w", err)
}
- if err := registerMDM(mux, scepCACerts[0], mdmStorage, checkinAndCommandService, logger); err != nil {
+ if err := registerMDM(mux, scepCACerts[0], mdmStorage, checkinAndCommandService, ddmService, logger); err != nil {
return fmt.Errorf("mdm: %w", err)
}
return nil
@@ -1085,6 +1086,7 @@ func registerMDM(
scepCACert *x509.Certificate,
mdmStorage nanomdm_storage.AllStorage,
checkinAndCommandService nanomdm_service.CheckinAndCommandService,
+ ddmService nanomdm_service.DeclarativeManagement,
logger kitlog.Logger,
) error {
certVerifier, err := certverify.NewPoolVerifier(
@@ -1104,7 +1106,7 @@ func registerMDM(
// enrollments and updates the Fleet hosts table accordingly with the UDID and serial number of
// the device.
// 5. Run actual MDM service operation (checkin handler or command and results handler).
- coreMDMService := nanomdm.New(mdmStorage, nanomdm.WithLogger(mdmLogger))
+ coreMDMService := nanomdm.New(mdmStorage, nanomdm.WithLogger(mdmLogger), nanomdm.WithDeclarativeManagement(ddmService))
// NOTE: it is critical that the coreMDMService runs first, as the first
// service in the multi-service feature is run to completion _before_ running
// the other ones in parallel. This way, subsequent services have access to
diff --git a/server/service/integration_ddm_test.go b/server/service/integration_ddm_test.go
new file mode 100644
index 0000000000..e8c77a192d
--- /dev/null
+++ b/server/service/integration_ddm_test.go
@@ -0,0 +1,909 @@
+package service
+
+import (
+ "bytes"
+ "context"
+ "crypto/md5" // nolint:gosec // used only for tests
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/fleetdm/fleet/v4/pkg/mdm/mdmtest"
+ "github.com/fleetdm/fleet/v4/server/datastore/mysql"
+ "github.com/fleetdm/fleet/v4/server/fleet"
+ "github.com/fleetdm/fleet/v4/server/ptr"
+ kitlog "github.com/go-kit/log"
+ "github.com/google/uuid"
+ "github.com/jmoiron/sqlx"
+ "github.com/stretchr/testify/require"
+)
+
+func (s *integrationMDMTestSuite) TestAppleDDMBatchUpload() {
+ t := s.T()
+ tmpl := `
+{
+ "Type": "com.apple.configuration.decl%d",
+ "Identifier": "com.fleet.config%d",
+ "Payload": {
+ "ServiceType": "com.apple.bash",
+ "DataAssetReference": "com.fleet.asset.bash" %s
+ }
+}`
+ // TODO: figure out the best way to do this. We might even consider
+ // starting a different test suite.
+ t.Cleanup(func() { s.cleanupDeclarations(t) })
+
+ newDeclBytes := func(i int, payload ...string) []byte {
+ var p string
+ if len(payload) > 0 {
+ p = "," + strings.Join(payload, ",")
+ }
+ return []byte(fmt.Sprintf(tmpl, i, i, p))
+ }
+
+ var decls [][]byte
+
+ for i := 0; i < 7; i++ {
+ decls = append(decls, newDeclBytes(i))
+ }
+
+ // Non-configuration type should fail
+ res := s.Do("POST", "/api/latest/fleet/mdm/profiles/batch", batchSetMDMProfilesRequest{Profiles: []fleet.MDMProfileBatchPayload{
+ {Name: "bad", Contents: []byte(`{"Type": "com.apple.activation"}`)},
+ }}, http.StatusUnprocessableEntity)
+
+ errMsg := extractServerErrorText(res.Body)
+ require.Contains(t, errMsg, "Only configuration declarations (com.apple.configuration) are supported")
+
+ // "com.apple.configuration.softwareupdate.enforcement.specific" type should fail
+ res = s.Do("POST", "/api/latest/fleet/mdm/profiles/batch", batchSetMDMProfilesRequest{Profiles: []fleet.MDMProfileBatchPayload{
+ {Name: "bad2", Contents: []byte(`{"Type": "com.apple.configuration.softwareupdate.enforcement.specific"}`)},
+ }}, http.StatusUnprocessableEntity)
+
+ errMsg = extractServerErrorText(res.Body)
+ require.Contains(t, errMsg, "Declaration profile can’t include OS updates settings. To control these settings, go to OS updates.")
+
+ // Types from our list of forbidden types should fail
+ for ft := range fleet.ForbiddenDeclTypes {
+ res = s.Do("POST", "/api/latest/fleet/mdm/profiles/batch", batchSetMDMProfilesRequest{Profiles: []fleet.MDMProfileBatchPayload{
+ {Name: "bad2", Contents: []byte(fmt.Sprintf(`{"Type": "%s"}`, ft))},
+ }}, http.StatusUnprocessableEntity)
+
+ errMsg = extractServerErrorText(res.Body)
+ require.Contains(t, errMsg, "Only configuration declarations that don’t require an asset reference are supported.")
+ }
+
+ // "com.apple.configuration.management.status-subscriptions" type should fail
+ res = s.Do("POST", "/api/latest/fleet/mdm/profiles/batch", batchSetMDMProfilesRequest{Profiles: []fleet.MDMProfileBatchPayload{
+ {Name: "bad2", Contents: []byte(`{"Type": "com.apple.configuration.management.status-subscriptions"}`)},
+ }}, http.StatusUnprocessableEntity)
+
+ errMsg = extractServerErrorText(res.Body)
+ require.Contains(t, errMsg, "Declaration profile can’t include status subscription type. To get host’s vitals, please use queries and policies.")
+
+ // Two different payloads with the same name should fail
+ res = s.Do("POST", "/api/latest/fleet/mdm/profiles/batch", batchSetMDMProfilesRequest{Profiles: []fleet.MDMProfileBatchPayload{
+ {Name: "bad2", Contents: newDeclBytes(1, `"foo": "bar"`)},
+ {Name: "bad2", Contents: newDeclBytes(2, `"baz": "bing"`)},
+ }}, http.StatusUnprocessableEntity)
+ errMsg = extractServerErrorText(res.Body)
+ require.Contains(t, errMsg, "A declaration profile with this name already exists.")
+
+ // Same identifier should fail
+ res = s.Do("POST", "/api/latest/fleet/mdm/profiles/batch", batchSetMDMProfilesRequest{Profiles: []fleet.MDMProfileBatchPayload{
+ {Name: "N1", Contents: decls[0]},
+ {Name: "N2", Contents: decls[0]},
+ }}, http.StatusUnprocessableEntity)
+ errMsg = extractServerErrorText(res.Body)
+ require.Contains(t, errMsg, "A declaration profile with this identifier already exists.")
+
+ // Create 2 declarations
+ s.Do("POST", "/api/latest/fleet/mdm/profiles/batch", batchSetMDMProfilesRequest{Profiles: []fleet.MDMProfileBatchPayload{
+ {Name: "N1", Contents: decls[0]},
+ {Name: "N2", Contents: decls[1]},
+ }}, http.StatusNoContent)
+
+ var resp listMDMConfigProfilesResponse
+ s.DoJSON("GET", "/api/latest/fleet/mdm/profiles", &listMDMConfigProfilesRequest{}, http.StatusOK, &resp)
+
+ require.Len(t, resp.Profiles, 2)
+ require.Equal(t, "N1", resp.Profiles[0].Name)
+ require.Equal(t, "darwin", resp.Profiles[0].Platform)
+ require.Equal(t, "N2", resp.Profiles[1].Name)
+ require.Equal(t, "darwin", resp.Profiles[1].Platform)
+
+ // Create 2 new declarations. These should take the place of the first two.
+ s.Do("POST", "/api/latest/fleet/mdm/profiles/batch", batchSetMDMProfilesRequest{Profiles: []fleet.MDMProfileBatchPayload{
+ {Name: "N3", Contents: decls[2]},
+ {Name: "N4", Contents: decls[3]},
+ }}, http.StatusNoContent)
+
+ s.DoJSON("GET", "/api/latest/fleet/mdm/profiles", &listMDMConfigProfilesRequest{}, http.StatusOK, &resp)
+
+ require.Len(t, resp.Profiles, 2)
+ require.Equal(t, "N3", resp.Profiles[0].Name)
+ require.Equal(t, "darwin", resp.Profiles[0].Platform)
+ require.Equal(t, "N4", resp.Profiles[1].Name)
+ require.Equal(t, "darwin", resp.Profiles[1].Platform)
+
+ // replace only 1 declaration, the other one should be the same
+
+ s.Do("POST", "/api/latest/fleet/mdm/profiles/batch", batchSetMDMProfilesRequest{Profiles: []fleet.MDMProfileBatchPayload{
+ {Name: "N3", Contents: decls[2]},
+ {Name: "N5", Contents: decls[4]},
+ }}, http.StatusNoContent)
+
+ s.DoJSON("GET", "/api/latest/fleet/mdm/profiles", &listMDMConfigProfilesRequest{}, http.StatusOK, &resp)
+
+ require.Len(t, resp.Profiles, 2)
+ require.Equal(t, "N3", resp.Profiles[0].Name)
+ require.Equal(t, "darwin", resp.Profiles[0].Platform)
+ require.Equal(t, "N5", resp.Profiles[1].Name)
+ require.Equal(t, "darwin", resp.Profiles[1].Platform)
+
+ // update the declarations
+
+ s.Do("POST", "/api/latest/fleet/mdm/profiles/batch", batchSetMDMProfilesRequest{Profiles: []fleet.MDMProfileBatchPayload{
+ {Name: "N3", Contents: newDeclBytes(2, `"foo": "bar"`)},
+ {Name: "N5", Contents: newDeclBytes(4, `"bing": "baz"`)},
+ }}, http.StatusNoContent)
+
+ s.DoJSON("GET", "/api/latest/fleet/mdm/profiles", &listMDMConfigProfilesRequest{}, http.StatusOK, &resp)
+
+ require.Len(t, resp.Profiles, 2)
+ require.Equal(t, "N3", resp.Profiles[0].Name)
+ require.Equal(t, "darwin", resp.Profiles[0].Platform)
+ require.Equal(t, "N5", resp.Profiles[1].Name)
+ require.Equal(t, "darwin", resp.Profiles[1].Platform)
+
+ var createResp createLabelResponse
+ s.DoJSON("POST", "/api/latest/fleet/labels", &fleet.LabelPayload{Name: ptr.String("label_1"), Query: ptr.String("select 1")}, http.StatusOK, &createResp)
+ require.NotZero(t, createResp.Label.ID)
+ require.Equal(t, "label_1", createResp.Label.Name)
+ lbl1 := createResp.Label.Label
+
+ s.DoJSON("POST", "/api/latest/fleet/labels", &fleet.LabelPayload{Name: ptr.String("label_2"), Query: ptr.String("select 1")}, http.StatusOK, &createResp)
+ require.NotZero(t, createResp.Label.ID)
+ require.Equal(t, "label_2", createResp.Label.Name)
+ lbl2 := createResp.Label.Label
+
+ // Add with labels
+ s.Do("POST", "/api/latest/fleet/mdm/profiles/batch", batchSetMDMProfilesRequest{Profiles: []fleet.MDMProfileBatchPayload{
+ {Name: "N5", Contents: decls[5], Labels: []string{lbl1.Name, lbl2.Name}},
+ {Name: "N6", Contents: decls[6], Labels: []string{lbl1.Name}},
+ }}, http.StatusNoContent)
+
+ s.DoJSON("GET", "/api/latest/fleet/mdm/profiles", &listMDMConfigProfilesRequest{}, http.StatusOK, &resp)
+
+ require.Len(t, resp.Profiles, 2)
+ require.Equal(t, "N5", resp.Profiles[0].Name)
+ require.Equal(t, "darwin", resp.Profiles[0].Platform)
+ require.Equal(t, "N6", resp.Profiles[1].Name)
+ require.Equal(t, "darwin", resp.Profiles[1].Platform)
+ require.Len(t, resp.Profiles[0].Labels, 2)
+ require.Equal(t, lbl1.Name, resp.Profiles[0].Labels[0].LabelName)
+ require.Equal(t, lbl2.Name, resp.Profiles[0].Labels[1].LabelName)
+ require.Len(t, resp.Profiles[1].Labels, 1)
+ require.Equal(t, lbl1.Name, resp.Profiles[1].Labels[0].LabelName)
+}
+
+func (s *integrationMDMTestSuite) TestMDMAppleDeviceManagementRequests() {
+ t := s.T()
+ _, mdmDevice := createHostThenEnrollMDM(s.ds, s.server.URL, t)
+
+ calcChecksum := func(source []byte) string {
+ csum := fmt.Sprintf("%x", md5.Sum(source)) //nolint:gosec
+ return strings.ToUpper(csum)
+ }
+
+ t.Cleanup(func() { s.cleanupDeclarations(t) })
+
+ insertDeclaration := func(t *testing.T, decl fleet.MDMAppleDeclaration) {
+ stmt := `
+INSERT INTO mdm_apple_declarations (
+ declaration_uuid,
+ team_id,
+ identifier,
+ name,
+ raw_json,
+ checksum,
+ created_at,
+ uploaded_at
+) VALUES (?,?,?,?,?,UNHEX(?),?,?)`
+
+ mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
+ _, err := q.ExecContext(context.Background(), stmt,
+ decl.DeclarationUUID,
+ decl.TeamID,
+ decl.Identifier,
+ decl.Name,
+ decl.RawJSON,
+ calcChecksum(decl.RawJSON),
+ decl.CreatedAt,
+ decl.UploadedAt,
+ )
+ return err
+ })
+ }
+
+ insertHostDeclaration := func(t *testing.T, hostUUID string, decl fleet.MDMAppleDeclaration) {
+ stmt := `
+INSERT INTO host_mdm_apple_declarations (
+ host_uuid,
+ status,
+ operation_type,
+ checksum,
+ declaration_uuid
+) VALUES (?,?,?,UNHEX(?),?)`
+
+ mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
+ _, err := q.ExecContext(context.Background(), stmt,
+ hostUUID,
+ fleet.MDMDeliveryPending,
+ fleet.MDMOperationTypeInstall,
+ calcChecksum(decl.RawJSON),
+ decl.DeclarationUUID,
+ )
+ return err
+ })
+ }
+
+ // initialize a time to use for our first declaration, subsequent declarations will be
+ // incremented by a minute
+ then := time.Now().UTC().Truncate(time.Second).Add(-1 * time.Hour)
+
+ // insert a declaration with no team
+ noTeamDeclsByUUID := map[string]fleet.MDMAppleDeclaration{
+ "123": {
+ DeclarationUUID: "123",
+ TeamID: ptr.Uint(0),
+ Identifier: "com.example",
+ Name: "Example",
+ RawJSON: json.RawMessage(`{
+ "Type": "com.apple.configuration.declaration-items.test",
+ "Payload": {"foo":"bar"},
+ "Identifier": "com.example"
+ }`),
+ CreatedAt: then,
+ UploadedAt: then,
+ },
+ }
+ insertDeclaration(t, noTeamDeclsByUUID["123"])
+ insertHostDeclaration(t, mdmDevice.UUID, noTeamDeclsByUUID["123"])
+
+ mapDeclsByChecksum := func(byUUID map[string]fleet.MDMAppleDeclaration) map[string]fleet.MDMAppleDeclaration {
+ byChecksum := make(map[string]fleet.MDMAppleDeclaration)
+ for _, d := range byUUID {
+ byChecksum[calcChecksum(d.RawJSON)] = byUUID[d.DeclarationUUID]
+ }
+ return byChecksum
+ }
+
+ parseTokensResp := func(r *http.Response) fleet.MDMAppleDDMTokensResponse {
+ require.NotNil(t, r)
+ b, err := io.ReadAll(r.Body)
+ require.NoError(t, err)
+ defer r.Body.Close()
+ r.Body = io.NopCloser(bytes.NewBuffer(b))
+ // t.Log("body", string(b))
+
+ // unmarsal the response to make sure it's valid
+ var tok fleet.MDMAppleDDMTokensResponse
+ err = json.NewDecoder(r.Body).Decode(&tok)
+ require.NoError(t, err)
+ // t.Log("decoded", tok)
+
+ return tok
+ }
+
+ parseDeclarationItemsResp := func(r *http.Response) fleet.MDMAppleDDMDeclarationItemsResponse {
+ require.NotNil(t, r)
+ b, err := io.ReadAll(r.Body)
+ require.NoError(t, err)
+ defer r.Body.Close()
+ r.Body = io.NopCloser(bytes.NewBuffer(b))
+ // t.Log("body", string(b))
+
+ // unmarsal the response to make sure it's valid
+ var di fleet.MDMAppleDDMDeclarationItemsResponse
+ err = json.NewDecoder(r.Body).Decode(&di)
+ require.NoError(t, err)
+ // t.Log("decoded", di)
+
+ return di
+ }
+
+ assertDeclarationResponse := func(r *http.Response, expected fleet.MDMAppleDeclaration) {
+ require.NotNil(t, r)
+
+ // unmarsal the response and assert it's valid
+ var wantParsed fleet.MDMAppleDDMDeclarationResponse
+ require.NoError(t, json.Unmarshal(expected.RawJSON, &wantParsed))
+ var gotParsed fleet.MDMAppleDDMDeclarationResponse
+ require.NoError(t, json.NewDecoder(r.Body).Decode(&gotParsed))
+ require.EqualValues(t, wantParsed.Payload, gotParsed.Payload)
+ require.Equal(t, calcChecksum(expected.RawJSON), gotParsed.ServerToken)
+ require.Equal(t, expected.Identifier, gotParsed.Identifier)
+ // t.Logf("decoded: %+v", gotParsed)
+ }
+
+ checkTokensResp := func(t *testing.T, r fleet.MDMAppleDDMTokensResponse, expectedTimestamp time.Time, prevToken string) {
+ require.Equal(t, expectedTimestamp, r.SyncTokens.Timestamp)
+ require.NotEmpty(t, r.SyncTokens.DeclarationsToken)
+ require.NotEqual(t, prevToken, r.SyncTokens.DeclarationsToken)
+ }
+
+ checkDeclarationItemsResp := func(t *testing.T, r fleet.MDMAppleDDMDeclarationItemsResponse, expectedDeclTok string, expectedDeclsByChecksum map[string]fleet.MDMAppleDeclaration) {
+ require.Equal(t, expectedDeclTok, r.DeclarationsToken)
+ // TODO(roberto): better assertions
+ require.NotEmpty(t, r.Declarations.Activations)
+ require.Empty(t, r.Declarations.Assets)
+ require.Empty(t, r.Declarations.Management)
+ require.Len(t, r.Declarations.Configurations, len(expectedDeclsByChecksum))
+ for _, m := range r.Declarations.Configurations {
+ d, ok := expectedDeclsByChecksum[m.ServerToken]
+ require.True(t, ok)
+ require.Equal(t, d.Identifier, m.Identifier)
+ }
+ }
+
+ checkRequestsDatabase := func(t *testing.T, messageType, enrollmentID string, expectedCount int) {
+ mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
+ var count int
+ if err := sqlx.GetContext(
+ context.Background(),
+ q,
+ &count,
+ "SELECT count(*) AS count FROM mdm_apple_declarative_requests WHERE enrollment_id = ? AND message_type = ?",
+ enrollmentID,
+ messageType,
+ ); err != nil {
+ return err
+ }
+
+ require.Equal(t, expectedCount, count, "unexpected db row count for declaration requests")
+
+ return nil
+ })
+ }
+
+ var currDeclToken string // we'll use this to track the expected token across tests
+
+ t.Run("Tokens", func(t *testing.T) {
+ checkRequestsDatabase(t, "tokens", mdmDevice.UUID, 0)
+ // get tokens, timestamp should be the same as the declaration and token should be non-empty
+ r, err := mdmDevice.DeclarativeManagement("tokens")
+ require.NoError(t, err)
+ parsed := parseTokensResp(r)
+ checkTokensResp(t, parsed, then, "")
+ currDeclToken = parsed.SyncTokens.DeclarationsToken
+
+ // insert a new declaration
+ noTeamDeclsByUUID["456"] = fleet.MDMAppleDeclaration{
+ DeclarationUUID: "456",
+ TeamID: ptr.Uint(0),
+ Identifier: "com.example2",
+ Name: "Example2",
+ RawJSON: json.RawMessage(`{
+ "Type": "com.apple.configuration.declaration-items.test",
+ "Payload": {"foo":"baz"},
+ "Identifier": "com.example2"
+ }`),
+ CreatedAt: then.Add(1 * time.Minute),
+ UploadedAt: then.Add(1 * time.Minute),
+ }
+ insertDeclaration(t, noTeamDeclsByUUID["456"])
+ insertHostDeclaration(t, mdmDevice.UUID, noTeamDeclsByUUID["456"])
+ checkRequestsDatabase(t, "tokens", mdmDevice.UUID, 1)
+
+ // get tokens again, timestamp and token should have changed
+ r, err = mdmDevice.DeclarativeManagement("tokens")
+ require.NoError(t, err)
+ parsed = parseTokensResp(r)
+ checkTokensResp(t, parsed, then.Add(1*time.Minute), currDeclToken)
+ currDeclToken = parsed.SyncTokens.DeclarationsToken
+ checkRequestsDatabase(t, "tokens", mdmDevice.UUID, 2)
+ })
+
+ t.Run("DeclarationItems", func(t *testing.T) {
+ checkRequestsDatabase(t, "declaration-items", mdmDevice.UUID, 0)
+ r, err := mdmDevice.DeclarativeManagement("declaration-items")
+ require.NoError(t, err)
+ checkDeclarationItemsResp(t, parseDeclarationItemsResp(r), currDeclToken, mapDeclsByChecksum(noTeamDeclsByUUID))
+
+ // insert a new declaration
+ noTeamDeclsByUUID["789"] = fleet.MDMAppleDeclaration{
+ DeclarationUUID: "789",
+ TeamID: ptr.Uint(0),
+ Identifier: "com.example3",
+ Name: "Example3",
+ RawJSON: json.RawMessage(`{
+ "Type": "com.apple.configuration.declaration-items.test",
+ "Payload": {"foo":"bang"},
+ "Identifier": "com.example3"
+ }`),
+ CreatedAt: then.Add(2 * time.Minute),
+ UploadedAt: then.Add(2 * time.Minute),
+ }
+ insertDeclaration(t, noTeamDeclsByUUID["789"])
+ insertHostDeclaration(t, mdmDevice.UUID, noTeamDeclsByUUID["789"])
+ checkRequestsDatabase(t, "declaration-items", mdmDevice.UUID, 1)
+
+ // get tokens again, timestamp and token should have changed
+ r, err = mdmDevice.DeclarativeManagement("tokens")
+ require.NoError(t, err)
+ toks := parseTokensResp(r)
+ checkTokensResp(t, toks, then.Add(2*time.Minute), currDeclToken)
+ currDeclToken = toks.SyncTokens.DeclarationsToken
+
+ r, err = mdmDevice.DeclarativeManagement("declaration-items")
+ require.NoError(t, err)
+ checkDeclarationItemsResp(t, parseDeclarationItemsResp(r), currDeclToken, mapDeclsByChecksum(noTeamDeclsByUUID))
+ checkRequestsDatabase(t, "declaration-items", mdmDevice.UUID, 2)
+ })
+
+ t.Run("Status", func(t *testing.T) {
+ checkRequestsDatabase(t, "status", mdmDevice.UUID, 0)
+ _, err := mdmDevice.DeclarativeManagement("status", fleet.MDMAppleDDMStatusReport{})
+ require.NoError(t, err)
+ checkRequestsDatabase(t, "status", mdmDevice.UUID, 1)
+ })
+
+ t.Run("Declaration", func(t *testing.T) {
+ want := noTeamDeclsByUUID["123"]
+ declarationPath := fmt.Sprintf("declaration/%s/%s", "configuration", want.Identifier)
+ checkRequestsDatabase(t, declarationPath, mdmDevice.UUID, 0)
+ r, err := mdmDevice.DeclarativeManagement(declarationPath)
+ require.NoError(t, err)
+
+ assertDeclarationResponse(r, want)
+
+ // insert a new declaration
+ noTeamDeclsByUUID["abc"] = fleet.MDMAppleDeclaration{
+ DeclarationUUID: "abc",
+ TeamID: ptr.Uint(0),
+ Identifier: "com.example4",
+ Name: "Example4",
+ RawJSON: json.RawMessage(`{
+ "Type": "com.apple.configuration.test",
+ "Payload": {"foo":"bar"},
+ "Identifier": "com.example4"
+ }`),
+ CreatedAt: then.Add(3 * time.Minute),
+ UploadedAt: then.Add(3 * time.Minute),
+ }
+ insertDeclaration(t, noTeamDeclsByUUID["abc"])
+ insertHostDeclaration(t, mdmDevice.UUID, noTeamDeclsByUUID["abc"])
+ want = noTeamDeclsByUUID["abc"]
+ r, err = mdmDevice.DeclarativeManagement(fmt.Sprintf("declaration/%s/%s", "configuration", want.Identifier))
+ require.NoError(t, err)
+ checkRequestsDatabase(t, declarationPath, mdmDevice.UUID, 1)
+
+ // try getting a non-existent declaration, should fail 404
+ nonExistantDeclarationPath := fmt.Sprintf("declaration/%s/%s", "configuration", "nonexistent")
+ checkRequestsDatabase(t, nonExistantDeclarationPath, mdmDevice.UUID, 0)
+ _, err = mdmDevice.DeclarativeManagement(nonExistantDeclarationPath)
+ require.Error(t, err)
+ require.ErrorContains(t, err, "404 Not Found")
+ checkRequestsDatabase(t, nonExistantDeclarationPath, mdmDevice.UUID, 1)
+
+ // typo should fail as bad request
+ typoDeclarationPath := fmt.Sprintf("declarations/%s/%s", "configurations", want.Identifier)
+ checkRequestsDatabase(t, typoDeclarationPath, mdmDevice.UUID, 0)
+ _, err = mdmDevice.DeclarativeManagement(typoDeclarationPath)
+ require.Error(t, err)
+ require.ErrorContains(t, err, "400 Bad Request")
+ checkRequestsDatabase(t, typoDeclarationPath, mdmDevice.UUID, 1)
+
+ assertDeclarationResponse(r, want)
+ })
+}
+
+func (s *integrationMDMTestSuite) TestAppleDDMReconciliation() {
+ t := s.T()
+ ctx := context.Background()
+ // TODO: use config logger or take into account FLEET_INTEGRATION_TESTS_DISABLE_LOG
+ logger := kitlog.NewJSONLogger(os.Stdout)
+
+ // TODO: use endpoints once those are available.
+ addDeclaration := func(identifier string, teamID uint) {
+ stmt := `
+ INSERT INTO mdm_apple_declarations
+ (declaration_uuid, team_id, identifier, name, raw_json, checksum)
+ VALUES
+ (UUID(), ?, ?, UUID(), ?, HEX(MD5(raw_json)) )`
+ mysql.ExecAdhocSQL(t, s.ds, func(tx sqlx.ExtContext) error {
+ _, err := tx.ExecContext(ctx, stmt, teamID, identifier, declarationForTest(identifier))
+ return err
+ })
+ }
+
+ deleteDeclaration := func(identifier string, teamID uint) {
+ mysql.ExecAdhocSQL(t, s.ds, func(tx sqlx.ExtContext) error {
+ _, err := tx.ExecContext(ctx, "DELETE FROM mdm_apple_declarations WHERE team_id = ? AND identifier = ?", teamID, identifier)
+ return err
+ })
+ }
+
+ // create a team
+ teamName := t.Name() + "team1"
+ team := &fleet.Team{
+ Name: teamName,
+ }
+ var createTeamResp teamResponse
+ s.DoJSON("POST", "/api/latest/fleet/teams", team, http.StatusOK, &createTeamResp)
+ require.NotZero(t, createTeamResp.Team.ID)
+ team = createTeamResp.Team
+
+ // TODO: figure out the best way to do this. We might even consider
+ // starting a different test suite.
+ t.Cleanup(func() { s.cleanupDeclarations(t) })
+
+ checkNoCommands := func(d *mdmtest.TestAppleMDMClient) {
+ cmd, err := d.Idle()
+ require.NoError(t, err)
+ require.Nil(t, cmd)
+ }
+
+ checkDDMSync := func(d *mdmtest.TestAppleMDMClient) {
+ cmd, err := d.Idle()
+ require.NoError(t, err)
+ require.NotNil(t, cmd)
+ require.Equal(t, "DeclarativeManagement", cmd.Command.RequestType)
+ cmd, err = d.Acknowledge(cmd.CommandUUID)
+ require.NoError(t, err)
+ require.Nil(t, cmd)
+ _, err = d.DeclarativeManagement("tokens")
+ require.NoError(t, err)
+ }
+
+ // create a windows host
+ _, err := s.ds.NewHost(context.Background(), &fleet.Host{
+ ID: 1,
+ OsqueryHostID: ptr.String("non-macos-host"),
+ NodeKey: ptr.String("non-macos-host"),
+ UUID: uuid.New().String(),
+ Hostname: fmt.Sprintf("%sfoo.local.non.macos", t.Name()),
+ Platform: "windows",
+ })
+ require.NoError(t, err)
+
+ // create a windows host that's enrolled in MDM
+ _, _ = createWindowsHostThenEnrollMDM(s.ds, s.server.URL, t)
+
+ // create a linux host
+ _, err = s.ds.NewHost(context.Background(), &fleet.Host{
+ ID: 2,
+ OsqueryHostID: ptr.String("linux-host"),
+ NodeKey: ptr.String("linux-host"),
+ UUID: uuid.New().String(),
+ Hostname: fmt.Sprintf("%sfoo.local.linux", t.Name()),
+ Platform: "linux",
+ })
+ require.NoError(t, err)
+
+ // create a host that's not enrolled into MDM
+ _, err = s.ds.NewHost(context.Background(), &fleet.Host{
+ ID: 2,
+ OsqueryHostID: ptr.String("not-mdm-enrolled"),
+ NodeKey: ptr.String("not-mdm-enrolled"),
+ UUID: uuid.New().String(),
+ Hostname: fmt.Sprintf("%sfoo.local.not.enrolled", t.Name()),
+ Platform: "darwin",
+ })
+ require.NoError(t, err)
+
+ // create a host and then enroll in MDM.
+ mdmHost, device := createHostThenEnrollMDM(s.ds, s.server.URL, t)
+
+ // trigger the reconciler, no error
+ err = ReconcileAppleDeclarations(ctx, s.ds, s.mdmCommander, logger)
+ require.NoError(t, err)
+
+ // declarativeManagement command is not sent.
+ checkNoCommands(device)
+
+ // add global declarations
+ addDeclaration("I1", 0)
+ addDeclaration("I2", 0)
+
+ // reconcile again, this time new declarations were added
+ err = ReconcileAppleDeclarations(ctx, s.ds, s.mdmCommander, logger)
+ require.NoError(t, err)
+
+ // TODO: check command is pending
+
+ // declarativeManagement command is sent
+ checkDDMSync(device)
+
+ // reconcile again, commands for the uploaded declarations are already sent
+ err = ReconcileAppleDeclarations(ctx, s.ds, s.mdmCommander, logger)
+ require.NoError(t, err)
+ // no new commands are sent
+ checkNoCommands(device)
+
+ // delete a declaration
+ deleteDeclaration("I1", 0)
+ // reconcile again
+ err = ReconcileAppleDeclarations(ctx, s.ds, s.mdmCommander, logger)
+ require.NoError(t, err)
+ // a DDM sync is triggered
+ checkDDMSync(device)
+
+ // add a new host
+ _, deviceTwo := createHostThenEnrollMDM(s.ds, s.server.URL, t)
+ // reconcile again
+ err = ReconcileAppleDeclarations(ctx, s.ds, s.mdmCommander, logger)
+ require.NoError(t, err)
+ // DDM sync is triggered only for the new host
+ checkNoCommands(device)
+ checkDDMSync(deviceTwo)
+
+ // add device to the team
+ s.Do("POST", "/api/v1/fleet/hosts/transfer",
+ addHostsToTeamRequest{TeamID: &team.ID, HostIDs: []uint{mdmHost.ID}}, http.StatusOK)
+
+ // reconcile
+ err = ReconcileAppleDeclarations(ctx, s.ds, s.mdmCommander, logger)
+ require.NoError(t, err)
+
+ // DDM sync is triggered only for the transferred host
+ // because the team doesn't have any declarations
+ checkDDMSync(device)
+ checkNoCommands(deviceTwo)
+
+ // reconcile
+ err = ReconcileAppleDeclarations(ctx, s.ds, s.mdmCommander, logger)
+ require.NoError(t, err)
+ // nobody receives commands this time
+ checkNoCommands(device)
+ checkNoCommands(deviceTwo)
+
+ // add declarations to the team
+ addDeclaration("I1", team.ID)
+ addDeclaration("I2", team.ID)
+
+ // reconcile
+ err = ReconcileAppleDeclarations(ctx, s.ds, s.mdmCommander, logger)
+ require.NoError(t, err)
+ // DDM sync is triggered for the host in the team
+ checkDDMSync(device)
+ checkNoCommands(deviceTwo)
+
+ // add a new host, this one belongs to the team
+ mdmHostThree, deviceThree := createHostThenEnrollMDM(s.ds, s.server.URL, t)
+ s.Do("POST", "/api/v1/fleet/hosts/transfer",
+ addHostsToTeamRequest{TeamID: &team.ID, HostIDs: []uint{mdmHostThree.ID}}, http.StatusOK)
+
+ // reconcile
+ err = ReconcileAppleDeclarations(ctx, s.ds, s.mdmCommander, logger)
+ require.NoError(t, err)
+ // DDM sync is triggered only for the new host
+ checkNoCommands(device)
+ checkNoCommands(deviceTwo)
+ checkDDMSync(deviceThree)
+
+ // no new commands after another reconciliation
+ err = ReconcileAppleDeclarations(ctx, s.ds, s.mdmCommander, logger)
+ require.NoError(t, err)
+ checkNoCommands(device)
+ checkNoCommands(deviceTwo)
+ checkNoCommands(deviceThree)
+
+ // TODO: use proper APIs for this
+ // add a new label + label declaration
+ addDeclaration("I3", team.ID)
+ label, err := s.ds.NewLabel(ctx, &fleet.Label{Name: t.Name(), Query: "select 1;"})
+ require.NoError(t, err)
+ // update label with host membership
+ mysql.ExecAdhocSQL(
+ t, s.ds, func(db sqlx.ExtContext) error {
+ _, err := db.ExecContext(
+ context.Background(),
+ "INSERT IGNORE INTO label_membership (host_id, label_id) VALUES (?, ?)",
+ mdmHostThree.ID,
+ label.ID,
+ )
+ return err
+ },
+ )
+
+ // update declaration <-> label mapping
+ mysql.ExecAdhocSQL(
+ t, s.ds, func(db sqlx.ExtContext) error {
+ _, err := db.ExecContext(
+ context.Background(),
+ `INSERT INTO
+ mdm_declaration_labels (apple_declaration_uuid, label_name, label_id)
+ VALUES ((SELECT declaration_uuid FROM mdm_apple_declarations WHERE team_id = ? and identifier = ?), ?, ?)`,
+ team.ID,
+ "I3",
+ label.Name,
+ label.ID,
+ )
+ return err
+ },
+ )
+
+ // reconcile
+ err = ReconcileAppleDeclarations(ctx, s.ds, s.mdmCommander, logger)
+ require.NoError(t, err)
+ // DDM sync is triggered only for the host with the label
+ checkNoCommands(device)
+ checkNoCommands(deviceTwo)
+ checkDDMSync(deviceThree)
+}
+
+func (s *integrationMDMTestSuite) TestAppleDDMStatusReport() {
+ t := s.T()
+ ctx := context.Background()
+ // TODO: use config logger or take into account FLEET_INTEGRATION_TESTS_DISABLE_LOG
+ logger := kitlog.NewJSONLogger(os.Stdout)
+
+ // TODO: figure out the best way to do this. We might even consider
+ // starting a different test suite.
+ t.Cleanup(func() { s.cleanupDeclarations(t) })
+
+ assertHostDeclarations := func(hostUUID string, wantDecls []*fleet.MDMAppleHostDeclaration) {
+ var gotDecls []*fleet.MDMAppleHostDeclaration
+ mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
+ return sqlx.SelectContext(context.Background(), q, &gotDecls, `SELECT declaration_identifier, status, operation_type FROM host_mdm_apple_declarations WHERE host_uuid = ?`, hostUUID)
+ })
+ require.ElementsMatch(t, wantDecls, gotDecls)
+ }
+
+ // create a host and then enroll in MDM.
+ mdmHost, device := createHostThenEnrollMDM(s.ds, s.server.URL, t)
+
+ declarations := []fleet.MDMProfileBatchPayload{
+ {Name: "N1.json", Contents: declarationForTest("I1")},
+ {Name: "N2.json", Contents: declarationForTest("I2")},
+ }
+ // add global declarations
+ s.Do("POST", "/api/v1/fleet/mdm/profiles/batch", batchSetMDMProfilesRequest{Profiles: declarations}, http.StatusNoContent)
+
+ // reconcile profiles
+ err := ReconcileAppleDeclarations(ctx, s.ds, s.mdmCommander, logger)
+ require.NoError(t, err)
+
+ // declarations are ("install", "pending") after the cron run
+ assertHostDeclarations(mdmHost.UUID, []*fleet.MDMAppleHostDeclaration{
+ {Identifier: "I1", Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ {Identifier: "I2", Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
+ })
+
+ // host gets a DDM sync call
+ cmd, err := device.Idle()
+ require.NoError(t, err)
+ require.Equal(t, "DeclarativeManagement", cmd.Command.RequestType)
+ _, err = device.Acknowledge(cmd.CommandUUID)
+ require.NoError(t, err)
+
+ r, err := device.DeclarativeManagement("declaration-items")
+ require.NoError(t, err)
+ body, err := io.ReadAll(r.Body)
+ require.NoError(t, err)
+ var items fleet.MDMAppleDDMDeclarationItemsResponse
+ require.NoError(t, json.Unmarshal(body, &items))
+
+ var i1ServerToken, i2ServerToken string
+ for _, d := range items.Declarations.Configurations {
+ switch d.Identifier {
+ case "I1":
+ i1ServerToken = d.ServerToken
+ case "I2":
+ i2ServerToken = d.ServerToken
+ }
+ }
+
+ // declarations are ("install", "verifying") after the ack
+ assertHostDeclarations(mdmHost.UUID, []*fleet.MDMAppleHostDeclaration{
+ {Identifier: "I1", Status: &fleet.MDMDeliveryVerifying, OperationType: fleet.MDMOperationTypeInstall},
+ {Identifier: "I2", Status: &fleet.MDMDeliveryVerifying, OperationType: fleet.MDMOperationTypeInstall},
+ })
+
+ // host sends a partial DDM report
+ report := fleet.MDMAppleDDMStatusReport{}
+ report.StatusItems.Management.Declarations.Configurations = []fleet.MDMAppleDDMStatusDeclaration{
+ {Active: true, Valid: fleet.MDMAppleDeclarationValid, Identifier: "I1", ServerToken: i1ServerToken},
+ }
+ _, err = device.DeclarativeManagement("status", report)
+ require.NoError(t, err)
+ assertHostDeclarations(mdmHost.UUID, []*fleet.MDMAppleHostDeclaration{
+ {Identifier: "I1", Status: &fleet.MDMDeliveryVerified, OperationType: fleet.MDMOperationTypeInstall},
+ {Identifier: "I2", Status: &fleet.MDMDeliveryVerifying, OperationType: fleet.MDMOperationTypeInstall},
+ })
+
+ // host sends a report with a wrong (could be old) server token for I2, nothing changes
+ report = fleet.MDMAppleDDMStatusReport{}
+ report.StatusItems.Management.Declarations.Configurations = []fleet.MDMAppleDDMStatusDeclaration{
+ {Active: true, Valid: fleet.MDMAppleDeclarationValid, Identifier: "I2", ServerToken: "foo"},
+ }
+ _, err = device.DeclarativeManagement("status", report)
+ require.NoError(t, err)
+ assertHostDeclarations(mdmHost.UUID, []*fleet.MDMAppleHostDeclaration{
+ {Identifier: "I1", Status: &fleet.MDMDeliveryVerified, OperationType: fleet.MDMOperationTypeInstall},
+ {Identifier: "I2", Status: &fleet.MDMDeliveryVerifying, OperationType: fleet.MDMOperationTypeInstall},
+ })
+
+ // host sends a full report, declaration I2 is invalid
+ report = fleet.MDMAppleDDMStatusReport{}
+ report.StatusItems.Management.Declarations.Configurations = []fleet.MDMAppleDDMStatusDeclaration{
+ {Active: true, Valid: fleet.MDMAppleDeclarationValid, Identifier: "I1", ServerToken: i1ServerToken},
+ {Active: false, Valid: fleet.MDMAppleDeclarationInvalid, Identifier: "I2", ServerToken: i2ServerToken},
+ }
+ _, err = device.DeclarativeManagement("status", report)
+ require.NoError(t, err)
+ assertHostDeclarations(mdmHost.UUID, []*fleet.MDMAppleHostDeclaration{
+ {Identifier: "I1", Status: &fleet.MDMDeliveryVerified, OperationType: fleet.MDMOperationTypeInstall},
+ {Identifier: "I2", Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeInstall},
+ })
+
+ // do a batch request, this time I2 is deleted
+ declarations = []fleet.MDMProfileBatchPayload{
+ {Name: "N1.json", Contents: declarationForTest("I1")},
+ }
+ s.Do("POST", "/api/v1/fleet/mdm/profiles/batch", batchSetMDMProfilesRequest{Profiles: declarations}, http.StatusNoContent)
+
+ // reconcile profiles
+ err = ReconcileAppleDeclarations(ctx, s.ds, s.mdmCommander, logger)
+ require.NoError(t, err)
+ assertHostDeclarations(mdmHost.UUID, []*fleet.MDMAppleHostDeclaration{
+ {Identifier: "I1", Status: &fleet.MDMDeliveryVerified, OperationType: fleet.MDMOperationTypeInstall},
+ {Identifier: "I2", Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
+ })
+
+ // host sends a report, declaration I2 is removed from the hosts_* table
+ report = fleet.MDMAppleDDMStatusReport{}
+ report.StatusItems.Management.Declarations.Configurations = []fleet.MDMAppleDDMStatusDeclaration{
+ {Active: true, Valid: fleet.MDMAppleDeclarationValid, Identifier: "I1", ServerToken: i1ServerToken},
+ }
+ _, err = device.DeclarativeManagement("status", report)
+ require.NoError(t, err)
+ assertHostDeclarations(mdmHost.UUID, []*fleet.MDMAppleHostDeclaration{
+ {Identifier: "I1", Status: &fleet.MDMDeliveryVerified, OperationType: fleet.MDMOperationTypeInstall},
+ })
+
+ // host sends a report, declaration I1 is failing after a while
+ report = fleet.MDMAppleDDMStatusReport{}
+ report.StatusItems.Management.Declarations.Configurations = []fleet.MDMAppleDDMStatusDeclaration{
+ {Active: false, Valid: fleet.MDMAppleDeclarationInvalid, Identifier: "I1", ServerToken: i1ServerToken},
+ }
+ _, err = device.DeclarativeManagement("status", report)
+ require.NoError(t, err)
+ assertHostDeclarations(mdmHost.UUID, []*fleet.MDMAppleHostDeclaration{
+ {Identifier: "I1", Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeInstall},
+ })
+}
+
+func declarationForTest(identifier string) []byte {
+ return []byte(fmt.Sprintf(`
+{
+ "Type": "com.apple.configuration.management.test",
+ "Payload": {
+ "Echo": "foo"
+ },
+ "Identifier": "%s"
+}`, identifier))
+}
+
+func (s *integrationMDMTestSuite) cleanupDeclarations(t *testing.T) {
+ ctx := context.Background()
+ // TODO: figure out the best way to do this. We might even consider
+ // starting a different test suite.
+ // delete declarations to not affect other tests
+ mysql.ExecAdhocSQL(t, s.ds, func(tx sqlx.ExtContext) error {
+ _, err := tx.ExecContext(ctx, "DELETE FROM mdm_apple_declarations")
+ return err
+ })
+ mysql.ExecAdhocSQL(t, s.ds, func(tx sqlx.ExtContext) error {
+ _, err := tx.ExecContext(ctx, "DELETE FROM host_mdm_apple_declarations")
+ return err
+ })
+
+}
diff --git a/server/service/integration_mdm_dep_test.go b/server/service/integration_mdm_dep_test.go
index 4e83e3c0fe..e3f36ea5d7 100644
--- a/server/service/integration_mdm_dep_test.go
+++ b/server/service/integration_mdm_dep_test.go
@@ -25,6 +25,7 @@ import (
"github.com/fleetdm/fleet/v4/server/worker"
kitlog "github.com/go-kit/log"
"github.com/google/uuid"
+ "github.com/groob/plist"
"github.com/jmoiron/sqlx"
micromdm "github.com/micromdm/micromdm/mdm/mdm"
"github.com/stretchr/testify/require"
@@ -264,7 +265,9 @@ func (s *integrationMDMTestSuite) runDEPEnrollReleaseDeviceTest(t *testing.T, de
//default:
// fmt.Println(">>>> device received command: ", cmd.CommandUUID, cmd.Command.RequestType)
//}
- cmds = append(cmds, cmd)
+ var fullCmd micromdm.CommandPayload
+ require.NoError(t, plist.Unmarshal(cmd.Raw, &fullCmd))
+ cmds = append(cmds, &fullCmd)
cmd, err = mdmDevice.Acknowledge(cmd.CommandUUID)
require.NoError(t, err)
}
@@ -327,7 +330,9 @@ func (s *integrationMDMTestSuite) runDEPEnrollReleaseDeviceTest(t *testing.T, de
cmd, err = mdmDevice.Idle()
require.NoError(t, err)
for cmd != nil {
- cmds = append(cmds, cmd)
+ var fullCmd micromdm.CommandPayload
+ require.NoError(t, plist.Unmarshal(cmd.Raw, &fullCmd))
+ cmds = append(cmds, &fullCmd)
cmd, err = mdmDevice.Acknowledge(cmd.CommandUUID)
require.NoError(t, err)
}
@@ -374,12 +379,14 @@ func (s *integrationMDMTestSuite) TestDEPProfileAssignment() {
cmd, err := mdmDevice.Idle()
require.NoError(t, err)
for cmd != nil {
- if cmd.Command.RequestType == "InstallEnterpriseApplication" &&
- cmd.Command.InstallEnterpriseApplication.ManifestURL != nil &&
- strings.Contains(*cmd.Command.InstallEnterpriseApplication.ManifestURL, apple_mdm.FleetdPublicManifestURL) {
- fleetdCmd = cmd
+ var fullCmd micromdm.CommandPayload
+ require.NoError(t, plist.Unmarshal(cmd.Raw, &fullCmd))
+ if fullCmd.Command.RequestType == "InstallEnterpriseApplication" &&
+ fullCmd.Command.InstallEnterpriseApplication.ManifestURL != nil &&
+ strings.Contains(*fullCmd.Command.InstallEnterpriseApplication.ManifestURL, apple_mdm.FleetdPublicManifestURL) {
+ fleetdCmd = &fullCmd
} else if cmd.Command.RequestType == "InstallProfile" {
- installProfileCmd = cmd
+ installProfileCmd = &fullCmd
}
cmd, err = mdmDevice.Acknowledge(cmd.CommandUUID)
require.NoError(t, err)
diff --git a/server/service/integration_mdm_test.go b/server/service/integration_mdm_test.go
index f439ce670e..017704ef02 100644
--- a/server/service/integration_mdm_test.go
+++ b/server/service/integration_mdm_test.go
@@ -1272,7 +1272,7 @@ func (s *integrationMDMTestSuite) TestWindowsProfileRetries() {
}
func checkNextPayloads(t *testing.T, mdmDevice *mdmtest.TestAppleMDMClient, forceDeviceErr bool) ([][]byte, []string) {
- var cmd *micromdm.CommandPayload
+ var cmd *mdm.Command
var err error
installs := [][]byte{}
removes := []string{}
@@ -1297,11 +1297,13 @@ func checkNextPayloads(t *testing.T, mdmDevice *mdmtest.TestAppleMDMClient, forc
break
}
+ var fullCmd micromdm.CommandPayload
+ require.NoError(t, plist.Unmarshal(cmd.Raw, &fullCmd))
switch cmd.Command.RequestType {
case "InstallProfile":
- installs = append(installs, cmd.Command.InstallProfile.Payload)
+ installs = append(installs, fullCmd.Command.InstallProfile.Payload)
case "RemoveProfile":
- removes = append(removes, cmd.Command.RemoveProfile.Identifier)
+ removes = append(removes, fullCmd.Command.RemoveProfile.Identifier)
}
}
@@ -2048,9 +2050,14 @@ func (s *integrationMDMTestSuite) TestAppleMDMDeviceEnrollment() {
mdmDeviceA := mdmtest.NewTestMDMClientAppleDirect(mdmEnrollInfo)
err := mdmDeviceA.Enroll()
require.NoError(t, err)
+ s.lastActivityOfTypeMatches(fleet.ActivityTypeMDMEnrolled{}.ActivityName(),
+ fmt.Sprintf(`{"host_serial": "%s", "host_display_name": "%s (%s)", "installed_from_dep": false, "mdm_platform": "apple"}`, mdmDeviceA.SerialNumber, mdmDeviceA.Model, mdmDeviceA.SerialNumber), 0)
+
mdmDeviceB := mdmtest.NewTestMDMClientAppleDirect(mdmEnrollInfo)
err = mdmDeviceB.Enroll()
require.NoError(t, err)
+ s.lastActivityOfTypeMatches(fleet.ActivityTypeMDMEnrolled{}.ActivityName(),
+ fmt.Sprintf(`{"host_serial": "%s", "host_display_name": "%s (%s)", "installed_from_dep": false, "mdm_platform": "apple"}`, mdmDeviceB.SerialNumber, mdmDeviceB.Model, mdmDeviceB.SerialNumber), 0)
// Find the ID of Fleet's MDM solution
var mdmID uint
@@ -2079,23 +2086,6 @@ func (s *integrationMDMTestSuite) TestAppleMDMDeviceEnrollment() {
}
}
- // Activities are generated for each device
- activities := listActivitiesResponse{}
- s.DoJSON("GET", "/api/latest/fleet/activities", nil, http.StatusOK, &activities, "order_key", "created_at")
- require.GreaterOrEqual(t, len(activities.Activities), 2)
-
- details := []*json.RawMessage{}
- for _, activity := range activities.Activities {
- if activity.Type == "mdm_enrolled" {
- require.Nil(t, activity.ActorID)
- require.Nil(t, activity.ActorFullName)
- details = append(details, activity.Details)
- }
- }
- require.Len(t, details, 2)
- require.JSONEq(t, fmt.Sprintf(`{"host_serial": "%s", "host_display_name": "%s (%s)", "installed_from_dep": false, "mdm_platform": "apple"}`, mdmDeviceA.SerialNumber, mdmDeviceA.Model, mdmDeviceA.SerialNumber), string(*details[len(details)-2]))
- require.JSONEq(t, fmt.Sprintf(`{"host_serial": "%s", "host_display_name": "%s (%s)", "installed_from_dep": false, "mdm_platform": "apple"}`, mdmDeviceB.SerialNumber, mdmDeviceB.Model, mdmDeviceB.SerialNumber), string(*details[len(details)-1]))
-
// set an enroll secret
var applyResp applyEnrollSecretSpecResponse
s.DoJSON("POST", "/api/latest/fleet/spec/enroll_secret", applyEnrollSecretSpecRequest{
@@ -2132,7 +2122,7 @@ func (s *integrationMDMTestSuite) TestAppleMDMDeviceEnrollment() {
require.NoError(t, err)
// An activity is created
- activities = listActivitiesResponse{}
+ activities := listActivitiesResponse{}
s.DoJSON("GET", "/api/latest/fleet/activities", nil, http.StatusOK, &activities)
found := false
@@ -2141,7 +2131,6 @@ func (s *integrationMDMTestSuite) TestAppleMDMDeviceEnrollment() {
found = true
require.Nil(t, activity.ActorID)
require.Nil(t, activity.ActorFullName)
- details = append(details, activity.Details)
require.JSONEq(t, fmt.Sprintf(`{"host_serial": "%s", "host_display_name": "%s (%s)", "installed_from_dep": false}`, mdmDeviceA.SerialNumber, mdmDeviceA.Model, mdmDeviceA.SerialNumber), string(*activity.Details))
}
}
@@ -5168,9 +5157,13 @@ func (s *integrationMDMTestSuite) TestBootstrapPackageStatus() {
cmd, err := d.device.Idle()
require.NoError(t, err)
for cmd != nil {
+ var fullCmd micromdm.CommandPayload
+ require.NoError(t, plist.Unmarshal(cmd.Raw, &fullCmd))
+
// if the command is to install the bootstrap package
- if manifest := cmd.Command.InstallEnterpriseApplication.Manifest; manifest != nil {
+ if manifest := fullCmd.Command.InstallEnterpriseApplication.Manifest; manifest != nil {
require.Equal(t, "InstallEnterpriseApplication", cmd.Command.RequestType)
+ require.NotNil(t, manifest)
require.Equal(t, "software-package", (*manifest).ManifestItems[0].Assets[0].Kind)
wantURL, err := bp.URL(s.server.URL)
require.NoError(t, err)
@@ -6867,7 +6860,7 @@ func (s *integrationMDMTestSuite) TestSSO() {
s.runWorker()
// ask for commands and verify that we get AccountConfiguration
- var accCmd *micromdm.CommandPayload
+ var accCmd *mdm.Command
cmd, err := mdmDevice.Idle()
require.NoError(t, err)
for cmd != nil {
@@ -6879,9 +6872,12 @@ func (s *integrationMDMTestSuite) TestSSO() {
}
require.NotNil(t, accCmd)
require.NotNil(t, accCmd.Command)
- require.True(t, accCmd.Command.AccountConfiguration.LockPrimaryAccountInfo)
- require.Equal(t, "SSO User 1", accCmd.Command.AccountConfiguration.PrimaryAccountFullName)
- require.Equal(t, "sso_user", accCmd.Command.AccountConfiguration.PrimaryAccountUserName)
+
+ var fullAccCmd *micromdm.CommandPayload
+ require.NoError(t, plist.Unmarshal(accCmd.Raw, &fullAccCmd))
+ require.True(t, fullAccCmd.Command.AccountConfiguration.LockPrimaryAccountInfo)
+ require.Equal(t, "SSO User 1", fullAccCmd.Command.AccountConfiguration.PrimaryAccountFullName)
+ require.Equal(t, "sso_user", fullAccCmd.Command.AccountConfiguration.PrimaryAccountUserName)
// report host details for the device
var hostResp getHostResponse
@@ -8725,6 +8721,8 @@ func (s *integrationMDMTestSuite) TestMDMConfigProfileCRUD() {
testTeam, err := s.ds.NewTeam(ctx, &fleet.Team{Name: "TestTeam"})
require.NoError(t, err)
+ t.Cleanup(func() { s.cleanupDeclarations(t) })
+
assertAppleProfile := func(filename, name, ident string, teamID uint, labelNames []string, wantStatus int, wantErrMsg string) string {
fields := map[string][]string{
"labels": labelNames,
@@ -8750,6 +8748,39 @@ func (s *integrationMDMTestSuite) TestMDMConfigProfileCRUD() {
require.Equal(t, "a", string(resp.ProfileUUID[0]))
return resp.ProfileUUID
}
+ assertAppleDeclaration := func(filename, ident string, teamID uint, labelNames []string, wantStatus int, wantErrMsg string) string {
+ fields := map[string][]string{
+ "labels": labelNames,
+ }
+ if teamID > 0 {
+ fields["team_id"] = []string{fmt.Sprintf("%d", teamID)}
+ }
+
+ bytes := []byte(fmt.Sprintf(`{
+ "Type": "com.apple.configuration.foo",
+ "Payload": {
+ "Echo": "f1337"
+ },
+ "Identifier": "%s"
+}`, ident))
+
+ body, headers := generateNewProfileMultipartRequest(t, filename, bytes, s.token, fields)
+ res := s.DoRawWithHeaders("POST", "/api/latest/fleet/configuration_profiles", body.Bytes(), wantStatus, headers)
+
+ if wantErrMsg != "" {
+ errMsg := extractServerErrorText(res.Body)
+ require.Contains(t, errMsg, wantErrMsg)
+ return ""
+ }
+
+ var resp newMDMConfigProfileResponse
+ err := json.NewDecoder(res.Body).Decode(&resp)
+ require.NoError(t, err)
+ require.NotEmpty(t, resp.ProfileUUID)
+ require.Equal(t, fleet.MDMAppleDeclarationUUIDPrefix, string(resp.ProfileUUID[0]))
+ return resp.ProfileUUID
+ }
+
createAppleProfile := func(name, ident string, teamID uint, labelNames []string) string {
uid := assertAppleProfile(name+".mobileconfig", name, ident, teamID, labelNames, http.StatusOK, "")
@@ -8764,6 +8795,20 @@ func (s *integrationMDMTestSuite) TestMDMConfigProfileCRUD() {
return uid
}
+ createAppleDeclaration := func(name, ident string, teamID uint, labelNames []string) string {
+ uid := assertAppleDeclaration(name+".json", ident, teamID, labelNames, http.StatusOK, "")
+
+ var wantJSON string
+ if teamID == 0 {
+ wantJSON = fmt.Sprintf(`{"team_id": null, "team_name": null, "profile_name": %q, "identifier": %q}`, name, ident)
+ } else {
+ wantJSON = fmt.Sprintf(`{"team_id": %d, "team_name": %q, "profile_name": %q, "identifier": %q}`, teamID, testTeam.Name, name, ident)
+ }
+ s.lastActivityOfTypeMatches(fleet.ActivityTypeCreatedDeclarationProfile{}.ActivityName(), wantJSON, 0)
+
+ return uid
+ }
+
assertWindowsProfile := func(filename, locURI string, teamID uint, labelNames []string, wantStatus int, wantErrMsg string) string {
fields := map[string][]string{
"labels": labelNames,
@@ -8831,9 +8876,27 @@ func (s *integrationMDMTestSuite) TestMDMConfigProfileCRUD() {
// but no conflict for no-team
assertAppleProfile("win-team-profile.mobileconfig", "win-team-profile", "test-team-ident-2", 0, nil, http.StatusOK, "")
+ // add some macOS declarations
+ createAppleDeclaration("apple-declaration", "test-declaration-ident", 0, nil)
+ // identifier must be unique, it conflicts with existing declaration
+ assertAppleDeclaration("apple-declaration.json", "test-declaration-ident", 0, nil, http.StatusConflict, "test-declaration-ident already exists")
+ // name is pulled from filename, it conflicts with existing declaration
+ assertAppleDeclaration("apple-declaration.json", "test-declaration-ident-2", 0, nil, http.StatusConflict, "apple-declaration already exists")
+ // uniqueness is checked only within team, so it's fine to have the same name and identifier in different teams
+ assertAppleDeclaration("apple-declaration.json", "test-declaration-ident", testTeam.ID, nil, http.StatusOK, "")
+ // name is pulled from filename, it conflicts with existing macOS config profile
+ assertAppleDeclaration("apple-global-profile.json", "test-declaration-ident-2", 0, nil, http.StatusConflict, "apple-global-profile already exists")
+ // name is pulled from filename, it conflicts with existing macOS config profile
+ assertAppleDeclaration("win-global-profile.json", "test-declaration-ident-2", 0, nil, http.StatusConflict, "win-global-profile already exists")
+ // windows profile name conflicts with existing declaration
+ assertWindowsProfile("apple-declaration.xml", "./Test", 0, nil, http.StatusConflict, "Couldn't upload. A configuration profile with this name already exists.")
+ // macOS profile name conflicts with existing declaration
+ assertAppleProfile("apple-declaration.mobileconfig", "apple-declaration", "test-declaration-ident", 0, nil, http.StatusConflict, "Couldn't upload. A configuration profile with this name already exists.")
+
// not an xml nor mobileconfig file
- assertWindowsProfile("foo.txt", "./Test", 0, nil, http.StatusBadRequest, "Couldn't upload. The file should be a .mobileconfig or .xml file.")
- assertAppleProfile("foo.txt", "foo", "foo-ident", 0, nil, http.StatusBadRequest, "Couldn't upload. The file should be a .mobileconfig or .xml file.")
+ assertWindowsProfile("foo.txt", "./Test", 0, nil, http.StatusBadRequest, "Couldn't add profile. The file should be a .mobileconfig, XML, or JSON file.")
+ assertAppleProfile("foo.txt", "foo", "foo-ident", 0, nil, http.StatusBadRequest, "Couldn't add profile. The file should be a .mobileconfig, XML, or JSON file.")
+ assertAppleDeclaration("foo.txt", "foo-ident", 0, nil, http.StatusBadRequest, "Couldn't add profile. The file should be a .mobileconfig, XML, or JSON file.")
// Windows-reserved LocURI
assertWindowsProfile("bitlocker.xml", syncml.FleetBitLockerTargetLocURI, 0, nil, http.StatusBadRequest, "Couldn't upload. Custom configuration profiles can't include BitLocker settings.")
@@ -8842,11 +8905,13 @@ func (s *integrationMDMTestSuite) TestMDMConfigProfileCRUD() {
// Fleet-reserved profiles
for name := range servermdm.FleetReservedProfileNames() {
assertAppleProfile(name+".mobileconfig", name, name+"-ident", 0, nil, http.StatusBadRequest, fmt.Sprintf(`name %s is not allowed`, name))
+ assertAppleDeclaration(name+".json", name+"-ident", 0, nil, http.StatusBadRequest, fmt.Sprintf(`name %q is not allowed`, name))
assertWindowsProfile(name+".xml", "./Test", 0, nil, http.StatusBadRequest, fmt.Sprintf(`Couldn't upload. Profile name %q is not allowed.`, name))
}
// profiles with non-existent labels
assertAppleProfile("apple-profile-with-labels.mobileconfig", "apple-profile-with-labels", "ident-with-labels", 0, []string{"does-not-exist"}, http.StatusBadRequest, "some or all the labels provided don't exist")
+ assertAppleDeclaration("apple-declaration-with-labels.json", "ident-with-labels", 0, []string{"does-not-exist"}, http.StatusBadRequest, "some or all the labels provided don't exist")
assertWindowsProfile("win-profile-with-labels.xml", "./Test", 0, []string{"does-not-exist"}, http.StatusBadRequest, "some or all the labels provided don't exist")
// create a couple of labels
@@ -8859,26 +8924,33 @@ func (s *integrationMDMTestSuite) TestMDMConfigProfileCRUD() {
// profiles mixing existent and non-existent labels
assertAppleProfile("apple-profile-with-labels.mobileconfig", "apple-profile-with-labels", "ident-with-labels", 0, []string{"does-not-exist", "foo"}, http.StatusBadRequest, "some or all the labels provided don't exist")
+ assertAppleDeclaration("apple-declaration-with-labels.json", "ident-with-labels", 0, []string{"does-not-exist", "foo"}, http.StatusBadRequest, "some or all the labels provided don't exist")
assertWindowsProfile("win-profile-with-labels.xml", "./Test", 0, []string{"does-not-exist", "bar"}, http.StatusBadRequest, "some or all the labels provided don't exist")
// profiles with valid labels
uuidAppleWithLabel := assertAppleProfile("apple-profile-with-labels.mobileconfig", "apple-profile-with-labels", "ident-with-labels", 0, []string{"foo"}, http.StatusOK, "")
+ uuidAppleDDMWithLabel := createAppleDeclaration("apple-decl-with-labels", "ident-decl-with-labels", 0, []string{"foo"})
uuidWindowsWithLabel := assertWindowsProfile("win-profile-with-labels.xml", "./Test", 0, []string{"foo", "bar"}, http.StatusOK, "")
// verify that the label associations have been created
// TODO: update when we have datastore methods to get this data
var profileLabels []fleet.ConfigurationProfileLabel
mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
- stmt := `SELECT COALESCE(apple_profile_uuid, windows_profile_uuid) as profile_uuid, label_name, label_id FROM mdm_configuration_profile_labels`
+ stmt := `
+ SELECT COALESCE(apple_profile_uuid, windows_profile_uuid) as profile_uuid, label_name, COALESCE(label_id, 0) as label_id
+ FROM mdm_configuration_profile_labels
+ UNION SELECT apple_declaration_uuid as profile_uuid, label_name, COALESCE(label_id, 0) as label_id
+ FROM mdm_declaration_labels ORDER BY profile_uuid, label_name;`
return sqlx.SelectContext(context.Background(), q, &profileLabels, stmt)
})
require.NotEmpty(t, profileLabels)
- require.Len(t, profileLabels, 3)
+ require.Len(t, profileLabels, 4)
require.ElementsMatch(
t,
[]fleet.ConfigurationProfileLabel{
{ProfileUUID: uuidAppleWithLabel, LabelName: labelFoo.Name, LabelID: labelFoo.ID},
+ {ProfileUUID: uuidAppleDDMWithLabel, LabelName: labelFoo.Name, LabelID: labelFoo.ID},
{ProfileUUID: uuidWindowsWithLabel, LabelName: labelFoo.Name, LabelID: labelFoo.ID},
{ProfileUUID: uuidWindowsWithLabel, LabelName: labelBar.Name, LabelID: labelBar.ID},
},
@@ -8891,19 +8963,27 @@ func (s *integrationMDMTestSuite) TestMDMConfigProfileCRUD() {
errMsg := extractServerErrorText(res.Body)
require.Contains(t, errMsg, "Couldn't upload. The file should include valid XML:")
- // Apple invalid content
+ // Apple invalid mobileconfig content
body, headers = generateNewProfileMultipartRequest(t,
"apple.mobileconfig", []byte("\x00\x01\x02"), s.token, nil)
res = s.DoRawWithHeaders("POST", "/api/latest/fleet/configuration_profiles", body.Bytes(), http.StatusBadRequest, headers)
errMsg = extractServerErrorText(res.Body)
require.Contains(t, errMsg, "mobileconfig is not XML nor PKCS7 parseable")
+ // Apple invalid json declaration
+ body, headers = generateNewProfileMultipartRequest(t,
+ "apple.json", []byte("{"), s.token, nil)
+ res = s.DoRawWithHeaders("POST", "/api/latest/fleet/configuration_profiles", body.Bytes(), http.StatusBadRequest, headers)
+ errMsg = extractServerErrorText(res.Body)
+ require.Contains(t, errMsg, "Couldn't upload. The file should include valid JSON:")
+
// get the existing profiles work
expectedProfiles := []fleet.MDMConfigProfilePayload{
{ProfileUUID: noTeamAppleProfUUID, Platform: "darwin", Name: "apple-global-profile", Identifier: "test-global-ident", TeamID: nil},
{ProfileUUID: teamAppleProfUUID, Platform: "darwin", Name: "apple-team-profile", Identifier: "test-team-ident", TeamID: &testTeam.ID},
{ProfileUUID: noTeamWinProfUUID, Platform: "windows", Name: "win-global-profile", TeamID: nil},
{ProfileUUID: teamWinProfUUID, Platform: "windows", Name: "win-team-profile", TeamID: &testTeam.ID},
+ {ProfileUUID: uuidAppleDDMWithLabel, Platform: "darwin", Name: "apple-decl-with-labels", Identifier: "ident-decl-with-labels", TeamID: nil, Labels: []fleet.ConfigurationProfileLabel{{LabelID: labelFoo.ID, LabelName: labelFoo.Name}}},
}
for _, prof := range expectedProfiles {
var getResp getMDMConfigProfileResponse
@@ -8922,8 +9002,10 @@ func (s *integrationMDMTestSuite) TestMDMConfigProfileCRUD() {
resp := s.Do("GET", fmt.Sprintf("/api/latest/fleet/configuration_profiles/%s", prof.ProfileUUID), nil, http.StatusOK, "alt", "media")
require.NotZero(t, resp.ContentLength)
require.Contains(t, resp.Header.Get("Content-Disposition"), "attachment;")
- if getResp.Platform == "darwin" {
+ if strings.HasPrefix(prof.ProfileUUID, "a") {
require.Contains(t, resp.Header.Get("Content-Type"), "application/x-apple-aspen-config")
+ } else if strings.HasPrefix(prof.ProfileUUID, fleet.MDMAppleDeclarationUUIDPrefix) {
+ require.Contains(t, resp.Header.Get("Content-Type"), "application/json")
} else {
require.Contains(t, resp.Header.Get("Content-Type"), "application/octet-stream")
}
@@ -8938,6 +9020,9 @@ func (s *integrationMDMTestSuite) TestMDMConfigProfileCRUD() {
// get an unknown Apple profile
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/configuration_profiles/%s", "ano-such-profile"), nil, http.StatusNotFound, &getResp)
s.Do("GET", fmt.Sprintf("/api/latest/fleet/configuration_profiles/%s", "ano-such-profile"), nil, http.StatusNotFound, "alt", "media")
+ // get an unknown Apple declaration
+ s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/configuration_profiles/%s", fmt.Sprintf("%sno-such-profile", fleet.MDMAppleDeclarationUUIDPrefix)), nil, http.StatusNotFound, &getResp)
+ s.Do("GET", fmt.Sprintf("/api/latest/fleet/configuration_profiles/%s", fmt.Sprintf("%sno-such-profile", fleet.MDMAppleDeclarationUUIDPrefix)), nil, http.StatusNotFound, "alt", "media")
// get an unknown Windows profile
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/configuration_profiles/%s", "wno-such-profile"), nil, http.StatusNotFound, &getResp)
s.Do("GET", fmt.Sprintf("/api/latest/fleet/configuration_profiles/%s", "wno-such-profile"), nil, http.StatusNotFound, "alt", "media")
@@ -8948,6 +9033,16 @@ func (s *integrationMDMTestSuite) TestMDMConfigProfileCRUD() {
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/configuration_profiles/%s", teamAppleProfUUID), nil, http.StatusOK, &deleteResp)
// delete non-existing Apple profile
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/configuration_profiles/%s", "ano-such-profile"), nil, http.StatusNotFound, &deleteResp)
+
+ // delete existing Apple declaration
+ s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/configuration_profiles/%s", uuidAppleDDMWithLabel), nil, http.StatusOK, &deleteResp)
+ s.lastActivityOfTypeMatches(
+ fleet.ActivityTypeDeletedDeclarationProfile{}.ActivityName(),
+ `{"profile_name": "apple-decl-with-labels", "identifier": "ident-decl-with-labels", "team_id": null, "team_name": null}`,
+ 0,
+ )
+ // delete non-existing Apple declaration
+ s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/configuration_profiles/%s", fmt.Sprintf("%sno-such-profile", fleet.MDMAppleDeclarationUUIDPrefix)), nil, http.StatusNotFound, &deleteResp)
// delete existing Windows profiles
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/configuration_profiles/%s", noTeamWinProfUUID), nil, http.StatusOK, &deleteResp)
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/configuration_profiles/%s", teamWinProfUUID), nil, http.StatusOK, &deleteResp)
@@ -8978,6 +9073,7 @@ func (s *integrationMDMTestSuite) TestMDMConfigProfileCRUD() {
return err
})
}
+ // TODO: Add tests for create/delete forbidden declaration types?
// make fleet add a FileVault profile
acResp := appConfigResponse{}
@@ -8999,6 +9095,8 @@ func (s *integrationMDMTestSuite) TestMDMConfigProfileCRUD() {
// try to delete the profile
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/configuration_profiles/%s", profUUID), nil, http.StatusBadRequest, &deleteResp)
+
+ // TODO: Add tests for OS updates declaration when implemented.
}
func (s *integrationMDMTestSuite) TestListMDMConfigProfiles() {
@@ -11029,10 +11127,12 @@ func (s *integrationMDMTestSuite) TestManualEnrollmentCommands() {
cmd, err := mdmDevice.Idle()
require.NoError(t, err)
for cmd != nil {
- if manifest := cmd.Command.InstallEnterpriseApplication.ManifestURL; manifest != nil {
+ var fullCmd micromdm.CommandPayload
+ require.NoError(t, plist.Unmarshal(cmd.Raw, &fullCmd))
+ if manifest := fullCmd.Command.InstallEnterpriseApplication.ManifestURL; manifest != nil {
foundInstallFleetdCommand = true
require.Equal(t, "InstallEnterpriseApplication", cmd.Command.RequestType)
- require.Contains(t, *cmd.Command.InstallEnterpriseApplication.ManifestURL, apple_mdm.FleetdPublicManifestURL)
+ require.Contains(t, *fullCmd.Command.InstallEnterpriseApplication.ManifestURL, apple_mdm.FleetdPublicManifestURL)
}
cmd, err = mdmDevice.Acknowledge(cmd.CommandUUID)
require.NoError(t, err)
@@ -11706,7 +11806,10 @@ func (s *integrationMDMTestSuite) TestDontIgnoreAnyProfileErrors() {
for cmd != nil {
if cmd.Command.RequestType == "RemoveProfile" {
var errChain []mdm.ErrorChain
- if cmd.Command.RemoveProfile.Identifier == "I1" {
+ var fullCmd micromdm.CommandPayload
+ require.NoError(t, plist.Unmarshal(cmd.Raw, &fullCmd))
+
+ if fullCmd.Command.RemoveProfile.Identifier == "I1" {
errChain = append(errChain, mdm.ErrorChain{ErrorCode: 89, ErrorDomain: "MDMClientError", USEnglishDescription: "Profile with identifier 'I1' not found."})
} else {
errChain = append(errChain, mdm.ErrorChain{ErrorCode: 96, ErrorDomain: "MDMClientError", USEnglishDescription: "Cannot replace profile 'I2' because it was not installed by the MDM server."})
@@ -11836,7 +11939,7 @@ func (s *integrationMDMTestSuite) TestSCEPCertExpiration() {
require.NoError(t, err)
checkRenewCertCommand := func(device *mdmtest.TestAppleMDMClient, enrollRef string) {
- var renewCmd *micromdm.CommandPayload
+ var renewCmd *mdm.Command
cmd, err := device.Idle()
require.NoError(t, err)
for cmd != nil {
@@ -11847,7 +11950,9 @@ func (s *integrationMDMTestSuite) TestSCEPCertExpiration() {
require.NoError(t, err)
}
require.NotNil(t, renewCmd)
- s.verifyEnrollmentProfile(renewCmd.Command.InstallProfile.Payload, enrollRef)
+ var fullCmd micromdm.CommandPayload
+ require.NoError(t, plist.Unmarshal(renewCmd.Raw, &fullCmd))
+ s.verifyEnrollmentProfile(fullCmd.Command.InstallProfile.Payload, enrollRef)
}
checkRenewCertCommand(manualEnrolledDevice, "")
diff --git a/server/service/mdm.go b/server/service/mdm.go
index 82c23aa710..63c71ba286 100644
--- a/server/service/mdm.go
+++ b/server/service/mdm.go
@@ -1019,6 +1019,25 @@ func getMDMConfigProfileEndpoint(ctx context.Context, request interface{}, svc f
}, nil
}
+ if isAppleDeclarationUUID(req.ProfileUUID) {
+ // TODO: we could potentially combined with the other service methods
+ decl, err := svc.GetMDMAppleDeclaration(ctx, req.ProfileUUID)
+ if err != nil {
+ return &getMDMConfigProfileResponse{Err: err}, nil
+ }
+
+ if downloadRequested {
+ return downloadFileResponse{
+ content: decl.RawJSON,
+ contentType: "application/json",
+ filename: fmt.Sprintf("%s_%s.json", time.Now().Format("2006-01-02"), strings.ReplaceAll(decl.Name, " ", "_")),
+ }, nil
+ }
+ return &getMDMConfigProfileResponse{
+ MDMConfigProfilePayload: fleet.NewMDMConfigProfilePayloadFromAppleDDM(decl),
+ }, nil
+ }
+
// Windows config profile
cp, err := svc.GetMDMWindowsConfigProfile(ctx, req.ProfileUUID)
if err != nil {
@@ -1077,6 +1096,9 @@ func deleteMDMConfigProfileEndpoint(ctx context.Context, request interface{}, sv
var err error
if isAppleProfileUUID(req.ProfileUUID) {
err = svc.DeleteMDMAppleConfigProfile(ctx, req.ProfileUUID)
+ } else if isAppleDeclarationUUID(req.ProfileUUID) {
+ // TODO: we could potentially combined with the other service methods
+ err = svc.DeleteMDMAppleDeclaration(ctx, req.ProfileUUID)
} else {
err = svc.DeleteMDMWindowsConfigProfile(ctx, req.ProfileUUID)
}
@@ -1157,6 +1179,10 @@ func isAppleProfileUUID(profileUUID string) bool {
return strings.HasPrefix(profileUUID, "a")
}
+func isAppleDeclarationUUID(profileUUID string) bool {
+ return strings.HasPrefix(profileUUID, fleet.MDMAppleDeclarationUUIDPrefix)
+}
+
////////////////////////////////////////////////////////////////////////////////
// POST /mdm/profiles (Create Apple or Windows MDM Config Profile)
////////////////////////////////////////////////////////////////////////////////
@@ -1221,7 +1247,23 @@ func newMDMConfigProfileEndpoint(ctx context.Context, request interface{}, svc f
defer ff.Close()
fileExt := filepath.Ext(req.Profile.Filename)
- if isApple := strings.EqualFold(fileExt, ".mobileconfig"); isApple {
+ profileName := strings.TrimSuffix(filepath.Base(req.Profile.Filename), fileExt)
+ isMobileConfig := strings.EqualFold(fileExt, ".mobileconfig")
+ isJSON := strings.EqualFold(fileExt, ".json")
+ if isMobileConfig || isJSON {
+ // Then it's an Apple configuration file
+ if isJSON {
+ decl, err := svc.NewMDMAppleDeclaration(ctx, req.TeamID, ff, req.Labels, profileName)
+ if err != nil {
+ return &newMDMConfigProfileResponse{Err: err}, nil
+ }
+
+ return &newMDMConfigProfileResponse{
+ ProfileUUID: decl.DeclarationUUID,
+ }, nil
+
+ }
+
cp, err := svc.NewMDMAppleConfigProfile(ctx, req.TeamID, ff, req.Labels)
if err != nil {
return &newMDMConfigProfileResponse{Err: err}, nil
@@ -1232,7 +1274,6 @@ func newMDMConfigProfileEndpoint(ctx context.Context, request interface{}, svc f
}
if isWindows := strings.EqualFold(fileExt, ".xml"); isWindows {
- profileName := strings.TrimSuffix(filepath.Base(req.Profile.Filename), fileExt)
cp, err := svc.NewMDMWindowsConfigProfile(ctx, req.TeamID, profileName, ff, req.Labels)
if err != nil {
return &newMDMConfigProfileResponse{Err: err}, nil
@@ -1254,7 +1295,7 @@ func (svc *Service) NewMDMUnsupportedConfigProfile(ctx context.Context, teamID u
// this is required because we need authorize to return the error, and
// svc.authz is only available on the concrete Service struct, not on the
// Service interface so it cannot be done in the endpoint itself.
- return &fleet.BadRequestError{Message: "Couldn't upload. The file should be a .mobileconfig or .xml file."}
+ return &fleet.BadRequestError{Message: "Couldn't add profile. The file should be a .mobileconfig, XML, or JSON file."}
}
func (svc *Service) NewMDMWindowsConfigProfile(ctx context.Context, teamID uint, profileName string, r io.Reader, labels []string) (*fleet.MDMWindowsConfigProfile, error) {
@@ -1476,7 +1517,7 @@ func (svc *Service) BatchSetMDMProfiles(
return ctxerr.Wrap(ctx, err, "validating labels")
}
- appleProfiles, err := getAppleProfiles(ctx, tmID, appCfg, profiles, labelMap)
+ appleProfiles, appleDecls, err := getAppleProfiles(ctx, tmID, appCfg, profiles, labelMap)
if err != nil {
return ctxerr.Wrap(ctx, err, "validating macOS profiles")
}
@@ -1490,7 +1531,7 @@ func (svc *Service) BatchSetMDMProfiles(
return nil
}
- if err := svc.ds.BatchSetMDMProfiles(ctx, tmID, appleProfiles, windowsProfiles); err != nil {
+ if err := svc.ds.BatchSetMDMProfiles(ctx, tmID, appleProfiles, windowsProfiles, appleDecls); err != nil {
return ctxerr.Wrap(ctx, err, "setting config profiles")
}
@@ -1576,17 +1617,94 @@ func getAppleProfiles(
appCfg *fleet.AppConfig,
profiles []fleet.MDMProfileBatchPayload,
labelMap map[string]fleet.ConfigurationProfileLabel,
-) ([]*fleet.MDMAppleConfigProfile, error) {
+) ([]*fleet.MDMAppleConfigProfile, []*fleet.MDMAppleDeclaration, error) {
// any duplicate identifier or name in the provided set results in an error
profs := make([]*fleet.MDMAppleConfigProfile, 0, len(profiles))
- byName, byIdent := make(map[string]bool, len(profiles)), make(map[string]bool, len(profiles))
+ decls := make([]*fleet.MDMAppleDeclaration, 0, len(profiles))
+ // we need to keep track of the names and identifiers to check for duplicates so we will use
+ // a map where the key is the name oridentifier and the value is either "mobileconfig" or
+ // "declaration" to differentiate between the two types of profiles
+ byName, byIdent := make(map[string]string, len(profiles)), make(map[string]string, len(profiles))
for _, prof := range profiles {
if mdm.GetRawProfilePlatform(prof.Contents) != "darwin" {
continue
}
+
+ // Check for DDM files
+
+ isJSON := func(b []byte) bool {
+ var js json.RawMessage
+ return json.Unmarshal(b, &js) == nil
+ }
+
+ // TODO(roberto): As a mini optimization, GetRawDeclarationValues could replace isJSON.
+ if isJSON(prof.Contents) {
+ rawDecl, err := fleet.GetRawDeclarationValues(prof.Contents)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if err := rawDecl.ValidateUserProvided(); err != nil {
+ return nil, nil, err
+ }
+
+ mdmDecl := fleet.NewMDMAppleDeclaration(prof.Contents, tmID, prof.Name, rawDecl.Type, rawDecl.Identifier)
+ for _, labelName := range prof.Labels {
+ if lbl, ok := labelMap[labelName]; ok {
+ declLabel := fleet.ConfigurationProfileLabel{
+ LabelName: lbl.LabelName,
+ LabelID: lbl.LabelID,
+ }
+ mdmDecl.Labels = append(mdmDecl.Labels, declLabel)
+ }
+ }
+
+ v, ok := byName[mdmDecl.Name]
+ switch {
+ case !ok:
+ byName[mdmDecl.Name] = "declaration"
+ case v == "mobileconfig":
+ return nil, nil, ctxerr.Wrap(ctx,
+ fleet.NewInvalidArgumentError(mdmDecl.Name, "A configuration profile with this name already exists."),
+ "duplicate mobileconfig profile by name")
+ case v == "declaration":
+ return nil, nil, ctxerr.Wrap(ctx,
+ fleet.NewInvalidArgumentError(mdmDecl.Name, "A declaration profile with this name already exists."),
+ "duplicate declaration profile by name")
+ default:
+ // this should never happen but just in case
+ return nil, nil, ctxerr.Wrap(ctx,
+ fleet.NewInvalidArgumentError(mdmDecl.Name, "A profile with this name already exists."),
+ "duplicate profile by name")
+ }
+
+ v, ok = byIdent[mdmDecl.Identifier]
+ switch {
+ case !ok:
+ byIdent[mdmDecl.Identifier] = "declaration"
+ case v == "mobileconfig":
+ return nil, nil, ctxerr.Wrap(ctx,
+ fleet.NewInvalidArgumentError(mdmDecl.Identifier, "A configuration profile with this identifier already exists."),
+ "duplicate mobileconfig profile by identifier")
+ case v == "declaration":
+ return nil, nil, ctxerr.Wrap(ctx,
+ fleet.NewInvalidArgumentError(mdmDecl.Identifier, "A declaration profile with this identifier already exists."),
+ "duplicate declaration profile by identifier")
+ default:
+ // this should never happen but just in case
+ return nil, nil, ctxerr.Wrap(ctx,
+ fleet.NewInvalidArgumentError(mdmDecl.Identifier, "A profile with this identifier already exists."),
+ "duplicate identifier by identifier")
+ }
+
+ decls = append(decls, mdmDecl)
+
+ continue
+ }
+
mdmProf, err := fleet.NewMDMAppleConfigProfile(prof.Contents, tmID)
if err != nil {
- return nil, ctxerr.Wrap(ctx,
+ return nil, nil, ctxerr.Wrap(ctx,
fleet.NewInvalidArgumentError(prof.Name, err.Error()),
"invalid mobileconfig profile")
}
@@ -1598,29 +1716,31 @@ func getAppleProfiles(
}
if err := mdmProf.ValidateUserProvided(); err != nil {
- return nil, ctxerr.Wrap(ctx,
+ return nil, nil, ctxerr.Wrap(ctx,
fleet.NewInvalidArgumentError(prof.Name, err.Error()))
}
if mdmProf.Name != prof.Name {
- return nil, ctxerr.Wrap(ctx,
+ return nil, nil, ctxerr.Wrap(ctx,
fleet.NewInvalidArgumentError(prof.Name, fmt.Sprintf("Couldn’t edit custom_settings. The name provided for the profile must match the profile PayloadDisplayName: %q", mdmProf.Name)),
"duplicate mobileconfig profile by name")
}
- if byName[mdmProf.Name] {
- return nil, ctxerr.Wrap(ctx,
+ // TODO: confirm error messages
+ if _, ok := byName[mdmProf.Name]; ok {
+ return nil, nil, ctxerr.Wrap(ctx,
fleet.NewInvalidArgumentError(prof.Name, fmt.Sprintf("Couldn’t edit custom_settings. More than one configuration profile have the same name (PayloadDisplayName): %q", mdmProf.Name)),
"duplicate mobileconfig profile by name")
}
- byName[mdmProf.Name] = true
+ byName[mdmProf.Name] = "mobileconfig"
- if byIdent[mdmProf.Identifier] {
- return nil, ctxerr.Wrap(ctx,
+ // TODO: confirm error messages
+ if _, ok := byIdent[mdmProf.Identifier]; ok {
+ return nil, nil, ctxerr.Wrap(ctx,
fleet.NewInvalidArgumentError(prof.Name, fmt.Sprintf("Couldn’t edit custom_settings. More than one configuration profile have the same identifier (PayloadIdentifier): %q", mdmProf.Identifier)),
"duplicate mobileconfig profile by identifier")
}
- byIdent[mdmProf.Identifier] = true
+ byIdent[mdmProf.Identifier] = "mobileconfig"
profs = append(profs, mdmProf)
}
@@ -1632,13 +1752,13 @@ func getAppleProfiles(
// custom_settings key, we just return a success response in this
// situation.
if len(profs) == 0 {
- return []*fleet.MDMAppleConfigProfile{}, nil
+ return []*fleet.MDMAppleConfigProfile{}, []*fleet.MDMAppleDeclaration{}, nil
}
- return nil, ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("mdm", "cannot set custom settings: Fleet MDM is not configured"))
+ return nil, nil, ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("mdm", "cannot set custom settings: Fleet MDM is not configured"))
}
- return profs, nil
+ return profs, decls, nil
}
func getWindowsProfiles(
diff --git a/server/service/mdm_test.go b/server/service/mdm_test.go
index 04bcb4e8d2..472981e518 100644
--- a/server/service/mdm_test.go
+++ b/server/service/mdm_test.go
@@ -753,6 +753,8 @@ func TestGetMDMDiskEncryptionSummary(t *testing.T) {
})
}
+// TODO: Add tests for Apple DDM authz?
+
func TestMDMWindowsConfigProfileAuthz(t *testing.T) {
ds := new(mock.Store)
// while the config profiles are not premium-only, teams are and we want to test with teams.
@@ -1093,7 +1095,7 @@ func TestMDMBatchSetProfiles(t *testing.T) {
ds.TeamFunc = func(ctx context.Context, id uint) (*fleet.Team, error) {
return &fleet.Team{ID: id, Name: "team"}, nil
}
- ds.BatchSetMDMProfilesFunc = func(ctx context.Context, tmID *uint, macProfiles []*fleet.MDMAppleConfigProfile, winProfiles []*fleet.MDMWindowsConfigProfile) error {
+ ds.BatchSetMDMProfilesFunc = func(ctx context.Context, tmID *uint, macProfiles []*fleet.MDMAppleConfigProfile, winProfiles []*fleet.MDMWindowsConfigProfile, macDecls []*fleet.MDMAppleDeclaration) error {
return nil
}
ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
@@ -1300,6 +1302,7 @@ func TestMDMBatchSetProfiles(t *testing.T) {
{Name: "N1", Contents: mobileconfigForTest("N1", "I1")},
{Name: "N2", Contents: mobileconfigForTest("N2", "I2")},
{Name: "N3", Contents: mobileconfigForTest("N3", "I3")},
+ {Name: "N4", Contents: declBytesForTest("D1", "d1content")},
},
``,
},
diff --git a/server/service/testing_utils.go b/server/service/testing_utils.go
index 0ceadc49aa..f6895dc4de 100644
--- a/server/service/testing_utils.go
+++ b/server/service/testing_utils.go
@@ -339,6 +339,10 @@ func RunServerForTestsWithDS(t *testing.T, ds fleet.Datastore, opts ...*TestServ
commander: apple_mdm.NewMDMAppleCommander(mdmStorage, mdmPusher),
logger: kitlog.NewNopLogger(),
},
+ &MDMAppleDDMService{
+ ds: ds,
+ logger: logger,
+ },
)
require.NoError(t, err)
}
diff --git a/terraform/byo-vpc/README.md b/terraform/byo-vpc/README.md
index 902b942aa2..27920c1819 100644
--- a/terraform/byo-vpc/README.md
+++ b/terraform/byo-vpc/README.md
@@ -33,7 +33,7 @@ No requirements.
|------|-------------|------|---------|:--------:|
| [alb\_config](#input\_alb\_config) | n/a | object({
name = optional(string, "fleet")
subnets = list(string)
security_groups = optional(list(string), [])
access_logs = optional(map(string), {})
certificate_arn = string
allowed_cidrs = optional(list(string), ["0.0.0.0/0"])
allowed_ipv6_cidrs = optional(list(string), ["::/0"])
egress_cidrs = optional(list(string), ["0.0.0.0/0"])
egress_ipv6_cidrs = optional(list(string), ["::/0"])
extra_target_groups = optional(any, [])
https_listener_rules = optional(any, [])
tls_policy = optional(string, "ELBSecurityPolicy-TLS-1-2-2017-01")
idle_timeout = optional(number, 60)
}) | n/a | yes |
| [ecs\_cluster](#input\_ecs\_cluster) | The config for the terraform-aws-modules/ecs/aws module | object({
autoscaling_capacity_providers = optional(any, {})
cluster_configuration = optional(any, {
execute_command_configuration = {
logging = "OVERRIDE"
log_configuration = {
cloud_watch_log_group_name = "/aws/ecs/aws-ec2"
}
}
})
cluster_name = optional(string, "fleet")
cluster_settings = optional(map(string), {
"name" : "containerInsights",
"value" : "enabled",
})
create = optional(bool, true)
default_capacity_provider_use_fargate = optional(bool, true)
fargate_capacity_providers = optional(any, {
FARGATE = {
default_capacity_provider_strategy = {
weight = 100
}
}
FARGATE_SPOT = {
default_capacity_provider_strategy = {
weight = 0
}
}
})
tags = optional(map(string))
}) | {
"autoscaling_capacity_providers": {},
"cluster_configuration": {
"execute_command_configuration": {
"log_configuration": {
"cloud_watch_log_group_name": "/aws/ecs/aws-ec2"
},
"logging": "OVERRIDE"
}
},
"cluster_name": "fleet",
"cluster_settings": {
"name": "containerInsights",
"value": "enabled"
},
"create": true,
"default_capacity_provider_use_fargate": true,
"fargate_capacity_providers": {
"FARGATE": {
"default_capacity_provider_strategy": {
"weight": 100
}
},
"FARGATE_SPOT": {
"default_capacity_provider_strategy": {
"weight": 0
}
}
},
"tags": {}
} | no |
-| [fleet\_config](#input\_fleet\_config) | The configuration object for Fleet itself. Fields that default to null will have their respective resources created if not specified. | object({
mem = optional(number, 4096)
cpu = optional(number, 512)
image = optional(string, "fleetdm/fleet:v4.47.2")
family = optional(string, "fleet")
sidecars = optional(list(any), [])
depends_on = optional(list(any), [])
mount_points = optional(list(any), [])
volumes = optional(list(any), [])
extra_environment_variables = optional(map(string), {})
extra_iam_policies = optional(list(string), [])
extra_execution_iam_policies = optional(list(string), [])
extra_secrets = optional(map(string), {})
security_groups = optional(list(string), null)
security_group_name = optional(string, "fleet")
iam_role_arn = optional(string, null)
service = optional(object({
name = optional(string, "fleet")
}), {
name = "fleet"
})
database = optional(object({
password_secret_arn = string
user = string
database = string
address = string
rr_address = optional(string, null)
}), {
password_secret_arn = null
user = null
database = null
address = null
rr_address = null
})
redis = optional(object({
address = string
use_tls = optional(bool, true)
}), {
address = null
use_tls = true
})
awslogs = optional(object({
name = optional(string, null)
region = optional(string, null)
create = optional(bool, true)
prefix = optional(string, "fleet")
retention = optional(number, 5)
}), {
name = null
region = null
prefix = "fleet"
retention = 5
})
loadbalancer = optional(object({
arn = string
}), {
arn = null
})
extra_load_balancers = optional(list(any), [])
networking = optional(object({
subnets = list(string)
security_groups = optional(list(string), null)
}), {
subnets = null
security_groups = null
})
autoscaling = optional(object({
max_capacity = optional(number, 5)
min_capacity = optional(number, 1)
memory_tracking_target_value = optional(number, 80)
cpu_tracking_target_value = optional(number, 80)
}), {
max_capacity = 5
min_capacity = 1
memory_tracking_target_value = 80
cpu_tracking_target_value = 80
})
iam = optional(object({
role = optional(object({
name = optional(string, "fleet-role")
policy_name = optional(string, "fleet-iam-policy")
}), {
name = "fleet-role"
policy_name = "fleet-iam-policy"
})
execution = optional(object({
name = optional(string, "fleet-execution-role")
policy_name = optional(string, "fleet-execution-role")
}), {
name = "fleet-execution-role"
policy_name = "fleet-iam-policy-execution"
})
}), {
name = "fleetdm-execution-role"
})
}) | {
"autoscaling": {
"cpu_tracking_target_value": 80,
"max_capacity": 5,
"memory_tracking_target_value": 80,
"min_capacity": 1
},
"awslogs": {
"create": true,
"name": null,
"prefix": "fleet",
"region": null,
"retention": 5
},
"cpu": 256,
"database": {
"address": null,
"database": null,
"password_secret_arn": null,
"rr_address": null,
"user": null
},
"depends_on": [],
"extra_environment_variables": {},
"extra_execution_iam_policies": [],
"extra_iam_policies": [],
"extra_load_balancers": [],
"extra_secrets": {},
"family": "fleet",
"iam": {
"execution": {
"name": "fleet-execution-role",
"policy_name": "fleet-iam-policy-execution"
},
"role": {
"name": "fleet-role",
"policy_name": "fleet-iam-policy"
}
},
"iam_role_arn": null,
"image": "fleetdm/fleet:v4.31.1",
"loadbalancer": {
"arn": null
},
"mem": 512,
"mount_points": [],
"networking": {
"security_groups": null,
"subnets": null
},
"redis": {
"address": null,
"use_tls": true
},
"security_group_name": "fleet",
"security_groups": null,
"service": {
"name": "fleet"
},
"sidecars": [],
"volumes": []
} | no |
+| [fleet\_config](#input\_fleet\_config) | The configuration object for Fleet itself. Fields that default to null will have their respective resources created if not specified. | object({
mem = optional(number, 4096)
cpu = optional(number, 512)
image = optional(string, "fleetdm/fleet:v4.47.3")
family = optional(string, "fleet")
sidecars = optional(list(any), [])
depends_on = optional(list(any), [])
mount_points = optional(list(any), [])
volumes = optional(list(any), [])
extra_environment_variables = optional(map(string), {})
extra_iam_policies = optional(list(string), [])
extra_execution_iam_policies = optional(list(string), [])
extra_secrets = optional(map(string), {})
security_groups = optional(list(string), null)
security_group_name = optional(string, "fleet")
iam_role_arn = optional(string, null)
service = optional(object({
name = optional(string, "fleet")
}), {
name = "fleet"
})
database = optional(object({
password_secret_arn = string
user = string
database = string
address = string
rr_address = optional(string, null)
}), {
password_secret_arn = null
user = null
database = null
address = null
rr_address = null
})
redis = optional(object({
address = string
use_tls = optional(bool, true)
}), {
address = null
use_tls = true
})
awslogs = optional(object({
name = optional(string, null)
region = optional(string, null)
create = optional(bool, true)
prefix = optional(string, "fleet")
retention = optional(number, 5)
}), {
name = null
region = null
prefix = "fleet"
retention = 5
})
loadbalancer = optional(object({
arn = string
}), {
arn = null
})
extra_load_balancers = optional(list(any), [])
networking = optional(object({
subnets = list(string)
security_groups = optional(list(string), null)
}), {
subnets = null
security_groups = null
})
autoscaling = optional(object({
max_capacity = optional(number, 5)
min_capacity = optional(number, 1)
memory_tracking_target_value = optional(number, 80)
cpu_tracking_target_value = optional(number, 80)
}), {
max_capacity = 5
min_capacity = 1
memory_tracking_target_value = 80
cpu_tracking_target_value = 80
})
iam = optional(object({
role = optional(object({
name = optional(string, "fleet-role")
policy_name = optional(string, "fleet-iam-policy")
}), {
name = "fleet-role"
policy_name = "fleet-iam-policy"
})
execution = optional(object({
name = optional(string, "fleet-execution-role")
policy_name = optional(string, "fleet-execution-role")
}), {
name = "fleet-execution-role"
policy_name = "fleet-iam-policy-execution"
})
}), {
name = "fleetdm-execution-role"
})
}) | {
"autoscaling": {
"cpu_tracking_target_value": 80,
"max_capacity": 5,
"memory_tracking_target_value": 80,
"min_capacity": 1
},
"awslogs": {
"create": true,
"name": null,
"prefix": "fleet",
"region": null,
"retention": 5
},
"cpu": 256,
"database": {
"address": null,
"database": null,
"password_secret_arn": null,
"rr_address": null,
"user": null
},
"depends_on": [],
"extra_environment_variables": {},
"extra_execution_iam_policies": [],
"extra_iam_policies": [],
"extra_load_balancers": [],
"extra_secrets": {},
"family": "fleet",
"iam": {
"execution": {
"name": "fleet-execution-role",
"policy_name": "fleet-iam-policy-execution"
},
"role": {
"name": "fleet-role",
"policy_name": "fleet-iam-policy"
}
},
"iam_role_arn": null,
"image": "fleetdm/fleet:v4.31.1",
"loadbalancer": {
"arn": null
},
"mem": 512,
"mount_points": [],
"networking": {
"security_groups": null,
"subnets": null
},
"redis": {
"address": null,
"use_tls": true
},
"security_group_name": "fleet",
"security_groups": null,
"service": {
"name": "fleet"
},
"sidecars": [],
"volumes": []
} | no |
| [migration\_config](#input\_migration\_config) | The configuration object for Fleet's migration task. | object({
mem = number
cpu = number
}) | {
"cpu": 1024,
"mem": 2048
} | no |
| [rds\_config](#input\_rds\_config) | The config for the terraform-aws-modules/rds-aurora/aws module | object({
name = optional(string, "fleet")
engine_version = optional(string, "8.0.mysql_aurora.3.02.2")
instance_class = optional(string, "db.t4g.large")
subnets = optional(list(string), [])
allowed_security_groups = optional(list(string), [])
allowed_cidr_blocks = optional(list(string), [])
apply_immediately = optional(bool, true)
monitoring_interval = optional(number, 10)
db_parameter_group_name = optional(string)
db_parameters = optional(map(string), {})
db_cluster_parameter_group_name = optional(string)
db_cluster_parameters = optional(map(string), {})
enabled_cloudwatch_logs_exports = optional(list(string), [])
master_username = optional(string, "fleet")
snapshot_identifier = optional(string)
cluster_tags = optional(map(string), {})
preferred_maintenance_window = optional(string, "thu:23:00-fri:00:00")
}) | {
"allowed_cidr_blocks": [],
"allowed_security_groups": [],
"apply_immediately": true,
"cluster_tags": {},
"db_cluster_parameter_group_name": null,
"db_cluster_parameters": {},
"db_parameter_group_name": null,
"db_parameters": {},
"enabled_cloudwatch_logs_exports": [],
"engine_version": "8.0.mysql_aurora.3.02.2",
"instance_class": "db.t4g.large",
"master_username": "fleet",
"monitoring_interval": 10,
"name": "fleet",
"preferred_maintenance_window": "thu:23:00-fri:00:00",
"snapshot_identifier": null,
"subnets": []
} | no |
| [redis\_config](#input\_redis\_config) | n/a | object({
name = optional(string, "fleet")
replication_group_id = optional(string)
elasticache_subnet_group_name = optional(string, "")
allowed_security_group_ids = optional(list(string), [])
subnets = list(string)
allowed_cidrs = list(string)
availability_zones = optional(list(string), [])
cluster_size = optional(number, 3)
instance_type = optional(string, "cache.m5.large")
apply_immediately = optional(bool, true)
automatic_failover_enabled = optional(bool, false)
engine_version = optional(string, "6.x")
family = optional(string, "redis6.x")
at_rest_encryption_enabled = optional(bool, true)
transit_encryption_enabled = optional(bool, true)
parameter = optional(list(object({
name = string
value = string
})), [])
log_delivery_configuration = optional(list(map(any)), [])
tags = optional(map(string), {})
}) | {
"allowed_cidrs": null,
"allowed_security_group_ids": [],
"apply_immediately": true,
"at_rest_encryption_enabled": true,
"automatic_failover_enabled": false,
"availability_zones": [],
"cluster_size": 3,
"elasticache_subnet_group_name": "",
"engine_version": "6.x",
"family": "redis6.x",
"instance_type": "cache.m5.large",
"log_delivery_configuration": [],
"name": "fleet",
"parameter": [],
"replication_group_id": null,
"subnets": null,
"tags": {},
"transit_encryption_enabled": true
} | no |
diff --git a/terraform/byo-vpc/byo-db/byo-ecs/variables.tf b/terraform/byo-vpc/byo-db/byo-ecs/variables.tf
index 0fac721d46..367832f457 100644
--- a/terraform/byo-vpc/byo-db/byo-ecs/variables.tf
+++ b/terraform/byo-vpc/byo-db/byo-ecs/variables.tf
@@ -13,7 +13,7 @@ variable "fleet_config" {
type = object({
mem = optional(number, 4096)
cpu = optional(number, 512)
- image = optional(string, "fleetdm/fleet:v4.47.2")
+ image = optional(string, "fleetdm/fleet:v4.47.3")
family = optional(string, "fleet")
sidecars = optional(list(any), [])
depends_on = optional(list(any), [])
diff --git a/terraform/byo-vpc/byo-db/variables.tf b/terraform/byo-vpc/byo-db/variables.tf
index 090ba32812..7c8ff1738f 100644
--- a/terraform/byo-vpc/byo-db/variables.tf
+++ b/terraform/byo-vpc/byo-db/variables.tf
@@ -74,7 +74,7 @@ variable "fleet_config" {
type = object({
mem = optional(number, 4096)
cpu = optional(number, 512)
- image = optional(string, "fleetdm/fleet:v4.47.2")
+ image = optional(string, "fleetdm/fleet:v4.47.3")
family = optional(string, "fleet")
sidecars = optional(list(any), [])
depends_on = optional(list(any), [])
diff --git a/terraform/byo-vpc/example/main.tf b/terraform/byo-vpc/example/main.tf
index 51c04fed72..daefdcbc54 100644
--- a/terraform/byo-vpc/example/main.tf
+++ b/terraform/byo-vpc/example/main.tf
@@ -17,7 +17,7 @@ provider "aws" {
}
locals {
- fleet_image = "fleetdm/fleet:v4.47.2"
+ fleet_image = "fleetdm/fleet:v4.47.3"
domain_name = "example.com"
}
diff --git a/terraform/byo-vpc/variables.tf b/terraform/byo-vpc/variables.tf
index e886db4212..f74561183f 100644
--- a/terraform/byo-vpc/variables.tf
+++ b/terraform/byo-vpc/variables.tf
@@ -167,7 +167,7 @@ variable "fleet_config" {
type = object({
mem = optional(number, 4096)
cpu = optional(number, 512)
- image = optional(string, "fleetdm/fleet:v4.47.2")
+ image = optional(string, "fleetdm/fleet:v4.47.3")
family = optional(string, "fleet")
sidecars = optional(list(any), [])
depends_on = optional(list(any), [])
diff --git a/terraform/example/main.tf b/terraform/example/main.tf
index 9339221dcb..5a2fcf5491 100644
--- a/terraform/example/main.tf
+++ b/terraform/example/main.tf
@@ -59,8 +59,8 @@ module "fleet" {
fleet_config = {
# To avoid pull-rate limiting from dockerhub, consider using our quay.io mirror
- # for the Fleet image. e.g. "quay.io/fleetdm/fleet:v4.47.2"
- image = "fleetdm/fleet:v4.47.2" # override default to deploy the image you desire
+ # for the Fleet image. e.g. "quay.io/fleetdm/fleet:v4.47.3"
+ image = "fleetdm/fleet:v4.47.3" # override default to deploy the image you desire
# See https://fleetdm.com/docs/deploy/reference-architectures#aws for appropriate scaling
# memory and cpu.
autoscaling = {
diff --git a/terraform/variables.tf b/terraform/variables.tf
index b77887ddb2..e8b6e29b67 100644
--- a/terraform/variables.tf
+++ b/terraform/variables.tf
@@ -215,7 +215,7 @@ variable "fleet_config" {
type = object({
mem = optional(number, 4096)
cpu = optional(number, 512)
- image = optional(string, "fleetdm/fleet:v4.47.2")
+ image = optional(string, "fleetdm/fleet:v4.47.3")
family = optional(string, "fleet")
sidecars = optional(list(any), [])
depends_on = optional(list(any), [])
diff --git a/tools/fleetctl-npm/package.json b/tools/fleetctl-npm/package.json
index af247c6058..19de34bf6c 100644
--- a/tools/fleetctl-npm/package.json
+++ b/tools/fleetctl-npm/package.json
@@ -1,6 +1,6 @@
{
"name": "fleetctl",
- "version": "v4.47.2",
+ "version": "v4.47.3",
"description": "Installer for the fleetctl CLI tool",
"bin": {
"fleetctl": "./run.js"
diff --git a/website/config/routes.js b/website/config/routes.js
index 4a69f469ae..6a486f1db6 100644
--- a/website/config/routes.js
+++ b/website/config/routes.js
@@ -511,6 +511,7 @@ module.exports.routes = {
'GET /learn-more-about/enrolling-hosts': '/docs/using-fleet/adding-hosts',
'GET /learn-more-about/setup-assistant': '/docs/using-fleet/mdm-macos-setup-experience#macos-setup-assistant',
'GET /learn-more-about/policy-automations': '/docs/using-fleet/automations',
+ 'GET /install-wine': 'https://github.com/fleetdm/fleet/blob/main/scripts/macos-install-wine.sh',
// Sitemap
// =============================================================================================================
diff --git a/website/views/pages/vulnerability-management.ejs b/website/views/pages/vulnerability-management.ejs
index 27c0205659..6971d60fe4 100644
--- a/website/views/pages/vulnerability-management.ejs
+++ b/website/views/pages/vulnerability-management.ejs
@@ -115,9 +115,10 @@
Up-to-date data without scans
-
Traditional network vulnerability scans can clog networks. Fleet does things differently.
+
Traditional network vulnerability scans can clog your network and even haunt your printers with pages full of wingdings. Fleet does things differently.
-
Lightweight enough for the most brittle environments (OT, data centers, embedded/BTS, low-latency gaming servers).
+
Eliminate the risk of side effects from scanning the network.
+
Lightweight enough for the most brittle environments (OT, data centers, embedded/BTS, low-latency gaming servers).
Quickly pull data about important CVEs and zero days during an incident or audit.