mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 00:45:19 +00:00
parent
44727ace3b
commit
f0ad942a57
@ -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"
|
||||
@ -390,7 +392,7 @@ func (c *TestAppleMDMClient) TokenUpdate() error {
|
||||
// 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) (*http.Response, error) {
|
||||
func (c *TestAppleMDMClient) DeclarativeManagement(endpoint string, data ...fleet.MDMAppleDDMStatusReport) (*http.Response, error) {
|
||||
payload := map[string]any{
|
||||
"MessageType": "DeclarativeManagement",
|
||||
"UDID": c.UUID,
|
||||
@ -398,6 +400,13 @@ func (c *TestAppleMDMClient) DeclarativeManagement(endpoint string) (*http.Respo
|
||||
"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
|
||||
}
|
||||
|
@ -3587,8 +3587,8 @@ func (ds *Datastore) MDMAppleGetHostsWithChangedDeclarations(ctx context.Context
|
||||
'install' as operation_type,
|
||||
ds.checksum,
|
||||
ds.declaration_uuid,
|
||||
ds.declaration_identifier as identifier,
|
||||
ds.declaration_name as name
|
||||
ds.declaration_identifier,
|
||||
ds.declaration_name
|
||||
FROM
|
||||
%s
|
||||
)
|
||||
@ -3599,8 +3599,8 @@ func (ds *Datastore) MDMAppleGetHostsWithChangedDeclarations(ctx context.Context
|
||||
'remove' as operation_type,
|
||||
hmae.checksum,
|
||||
hmae.declaration_uuid,
|
||||
hmae.declaration_identifier as identifier,
|
||||
hmae.declaration_name as name
|
||||
hmae.declaration_identifier,
|
||||
hmae.declaration_name
|
||||
FROM
|
||||
%s
|
||||
)
|
||||
@ -3615,3 +3615,80 @@ func (ds *Datastore) MDMAppleGetHostsWithChangedDeclarations(ctx context.Context
|
||||
}
|
||||
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'
|
||||
`
|
||||
|
||||
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")
|
||||
}
|
||||
|
@ -635,10 +635,10 @@ type MDMAppleHostDeclaration struct {
|
||||
DeclarationUUID string `db:"declaration_uuid" json:"profile_uuid"`
|
||||
|
||||
// Name corresponds to the file name of the associated JSON declaration payload.
|
||||
Name string `db:"name" json:"name"`
|
||||
Name string `db:"declaration_name" json:"name"`
|
||||
|
||||
// Identifier corresponds to the "Identifier" key of the associated declaration.
|
||||
Identifier string `db:"identifier" json:"-"`
|
||||
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"`
|
||||
@ -727,3 +727,94 @@ type MDMAppleDDMDeclarationResponse struct {
|
||||
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"`
|
||||
}
|
||||
|
@ -1171,6 +1171,16 @@ type Datastore interface {
|
||||
// MDMAppleBatchInsertHostDeclarations tracks the current status of all
|
||||
// the host declarations provided.
|
||||
MDMAppleBatchInsertHostDeclarations(ctx context.Context, changedDeclarations []*MDMAppleHostDeclaration) 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
|
||||
|
@ -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 {
|
||||
|
@ -768,6 +768,10 @@ type MDMAppleGetHostsWithChangedDeclarationsFunc func(ctx context.Context) ([]*f
|
||||
|
||||
type MDMAppleBatchInsertHostDeclarationsFunc func(ctx context.Context, changedDeclarations []*fleet.MDMAppleHostDeclaration) error
|
||||
|
||||
type MDMAppleStoreDDMStatusReportFunc func(ctx context.Context, hostUUID string, updates []*fleet.MDMAppleHostDeclaration) error
|
||||
|
||||
type MDMAppleSetDeclarationsAsVerifyingFunc func(ctx context.Context, hostUUID string) error
|
||||
|
||||
type WSTEPStoreCertificateFunc func(ctx context.Context, name string, crt *x509.Certificate) error
|
||||
|
||||
type WSTEPNewSerialFunc func(ctx context.Context) (*big.Int, error)
|
||||
@ -1998,6 +2002,12 @@ type DataStore struct {
|
||||
MDMAppleBatchInsertHostDeclarationsFunc MDMAppleBatchInsertHostDeclarationsFunc
|
||||
MDMAppleBatchInsertHostDeclarationsFuncInvoked bool
|
||||
|
||||
MDMAppleStoreDDMStatusReportFunc MDMAppleStoreDDMStatusReportFunc
|
||||
MDMAppleStoreDDMStatusReportFuncInvoked bool
|
||||
|
||||
MDMAppleSetDeclarationsAsVerifyingFunc MDMAppleSetDeclarationsAsVerifyingFunc
|
||||
MDMAppleSetDeclarationsAsVerifyingFuncInvoked bool
|
||||
|
||||
WSTEPStoreCertificateFunc WSTEPStoreCertificateFunc
|
||||
WSTEPStoreCertificateFuncInvoked bool
|
||||
|
||||
@ -4782,6 +4792,20 @@ func (s *DataStore) MDMAppleBatchInsertHostDeclarations(ctx context.Context, cha
|
||||
return s.MDMAppleBatchInsertHostDeclarationsFunc(ctx, changedDeclarations)
|
||||
}
|
||||
|
||||
func (s *DataStore) MDMAppleStoreDDMStatusReport(ctx context.Context, hostUUID string, updates []*fleet.MDMAppleHostDeclaration) error {
|
||||
s.mu.Lock()
|
||||
s.MDMAppleStoreDDMStatusReportFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.MDMAppleStoreDDMStatusReportFunc(ctx, hostUUID, updates)
|
||||
}
|
||||
|
||||
func (s *DataStore) MDMAppleSetDeclarationsAsVerifying(ctx context.Context, hostUUID string) error {
|
||||
s.mu.Lock()
|
||||
s.MDMAppleSetDeclarationsAsVerifyingFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.MDMAppleSetDeclarationsAsVerifyingFunc(ctx, hostUUID)
|
||||
}
|
||||
|
||||
func (s *DataStore) WSTEPStoreCertificate(ctx context.Context, name string, crt *x509.Certificate) error {
|
||||
s.mu.Lock()
|
||||
s.WSTEPStoreCertificateFuncInvoked = true
|
||||
|
@ -2615,7 +2615,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
|
||||
}
|
||||
|
||||
@ -2756,6 +2762,8 @@ func ReconcileAppleDeclarations(
|
||||
return ctxerr.Wrap(ctx, err, "issuing DeclarativeManagement command")
|
||||
}
|
||||
|
||||
logger.Log("msg", "sent DeclarativeManagement command", "host_number", len(uuids))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -3244,9 +3252,7 @@ func (svc *MDMAppleDDMService) DeclarativeManagement(r *mdm.Request, dm *mdm.Dec
|
||||
|
||||
case dm.Endpoint == "status":
|
||||
level.Debug(svc.logger).Log("msg", "received status request")
|
||||
// TODO(roberto): handle status
|
||||
|
||||
return nil, nil
|
||||
return nil, svc.handleDeclarationStatus(r.Context, dm)
|
||||
|
||||
case strings.HasPrefix(dm.Endpoint, "declaration/"):
|
||||
level.Debug(svc.logger).Log("msg", "received declarations request")
|
||||
@ -3374,3 +3380,56 @@ func (svc *MDMAppleDDMService) handleConfigurationDeclaration(ctx context.Contex
|
||||
}
|
||||
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
|
||||
}
|
||||
|
@ -12982,7 +12982,7 @@ INSERT INTO host_mdm_apple_declarations (
|
||||
})
|
||||
|
||||
t.Run("Status", func(t *testing.T) {
|
||||
_, err := mdmDevice.DeclarativeManagement("status")
|
||||
_, err := mdmDevice.DeclarativeManagement("status", fleet.MDMAppleDDMStatusReport{})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
@ -13266,6 +13266,144 @@ func (s *integrationMDMTestSuite) TestAppleDDMReconciliation() {
|
||||
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)
|
||||
|
||||
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(`
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user