diff --git a/changes/30853-fail-unknown-declaration-type-ddm-errors b/changes/30853-fail-unknown-declaration-type-ddm-errors new file mode 100644 index 0000000000..ee4e60decd --- /dev/null +++ b/changes/30853-fail-unknown-declaration-type-ddm-errors @@ -0,0 +1 @@ +* Mark DDM profiles as failed if response comes back with Unknown Declaration Type error, and improve upload validation for declaration type. \ No newline at end of file diff --git a/server/fleet/apple_mdm.go b/server/fleet/apple_mdm.go index 87044b9329..3fec868f2d 100644 --- a/server/fleet/apple_mdm.go +++ b/server/fleet/apple_mdm.go @@ -682,8 +682,8 @@ func (r *MDMAppleRawDeclaration) ValidateUserProvided() error { 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.") + if !strings.HasPrefix(r.Type, "com.apple.configuration.") { + return NewInvalidArgumentError(r.Type, "Only configuration declarations (com.apple.configuration.) are supported.") } return err diff --git a/server/service/apple_mdm.go b/server/service/apple_mdm.go index d096abc9bb..e3dba6ed8b 100644 --- a/server/service/apple_mdm.go +++ b/server/service/apple_mdm.go @@ -6393,7 +6393,7 @@ func (svc *MDMAppleDDMService) handleDeclarationStatus(ctx context.Context, dm * switch { case r.Active && r.Valid == fleet.MDMAppleDeclarationValid: status = fleet.MDMDeliveryVerified - case r.Valid == fleet.MDMAppleDeclarationInvalid: + case r.Valid == fleet.MDMAppleDeclarationInvalid || isUnknownDeclarationType(r): status = fleet.MDMDeliveryFailed detail = apple_mdm.FmtDDMError(r.Reasons) case r.Valid == fleet.MDMAppleDeclarationValid: // should be rare/never @@ -6441,6 +6441,14 @@ func (svc *MDMAppleDDMService) handleDeclarationStatus(ctx context.Context, dm * return nil } +// Checks the active, valid and first reason to verify if it is an unknown declaration type error +func isUnknownDeclarationType(declarationResponse fleet.MDMAppleDDMStatusDeclaration) bool { + return !declarationResponse.Active && + declarationResponse.Valid == fleet.MDMAppleDeclarationUnknown && + len(declarationResponse.Reasons) > 0 && + declarationResponse.Reasons[0].Code == "Error.UnknownDeclarationType" +} + //////////////////////////////////////////////////////////////////////////////// // Generate ABM keypair endpoint //////////////////////////////////////////////////////////////////////////////// diff --git a/server/service/apple_mdm_test.go b/server/service/apple_mdm_test.go index 014bca3b16..c6270f57d0 100644 --- a/server/service/apple_mdm_test.go +++ b/server/service/apple_mdm_test.go @@ -811,6 +811,11 @@ func TestNewMDMAppleDeclaration(t *testing.T) { _, err := svc.NewMDMAppleDeclaration(ctx, 0, bytes.NewReader(b), nil, "name", fleet.LabelsIncludeAll) assert.ErrorContains(t, err, "Fleet variable") + // decl type missing actual type + b = declarationForTestWithType("D1", "com.apple.configuration") + _, err = svc.NewMDMAppleDeclaration(ctx, 0, bytes.NewReader(b), nil, "name", fleet.LabelsIncludeAll) + assert.ErrorContains(t, err, "Only configuration declarations (com.apple.configuration.) are supported") + ds.NewMDMAppleDeclarationFunc = func(ctx context.Context, d *fleet.MDMAppleDeclaration) (*fleet.MDMAppleDeclaration, error) { return d, nil } diff --git a/server/service/integration_mdm_ddm_test.go b/server/service/integration_mdm_ddm_test.go index 43e1778dd3..7317fa9fc2 100644 --- a/server/service/integration_mdm_ddm_test.go +++ b/server/service/integration_mdm_ddm_test.go @@ -55,7 +55,7 @@ func (s *integrationMDMTestSuite) TestAppleDDMBatchUpload() { }}, http.StatusUnprocessableEntity) errMsg := extractServerErrorText(res.Body) - require.Contains(t, errMsg, "Only configuration declarations (com.apple.configuration) are supported") + 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{ @@ -976,6 +976,7 @@ func (s *integrationMDMTestSuite) TestAppleDDMStatusReport() { declarations := []fleet.MDMProfileBatchPayload{ {Name: "N1.json", Contents: declarationForTest("I1")}, {Name: "N2.json", Contents: declarationForTest("I2")}, + {Name: "Unknown.json", Contents: declarationForTestWithType("I3", "com.apple.configuration.")}, } // add global declarations s.Do("POST", "/api/v1/fleet/mdm/profiles/batch", batchSetMDMProfilesRequest{Profiles: declarations}, http.StatusNoContent) @@ -988,6 +989,7 @@ func (s *integrationMDMTestSuite) TestAppleDDMStatusReport() { assertHostDeclarations(mdmHost.UUID, []*fleet.MDMAppleHostDeclaration{ {Identifier: "I1", Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall}, {Identifier: "I2", Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall}, + {Identifier: "I3", Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall}, }) // host gets a DDM sync call @@ -1004,13 +1006,15 @@ func (s *integrationMDMTestSuite) TestAppleDDMStatusReport() { var items fleet.MDMAppleDDMDeclarationItemsResponse require.NoError(t, json.Unmarshal(body, &items)) - var i1ServerToken, i2ServerToken string + var i1ServerToken, i2ServerToken, i3ServerToken string for _, d := range items.Declarations.Configurations { switch d.Identifier { case "I1": i1ServerToken = d.ServerToken case "I2": i2ServerToken = d.ServerToken + case "I3": + i3ServerToken = d.ServerToken } } @@ -1018,6 +1022,7 @@ func (s *integrationMDMTestSuite) TestAppleDDMStatusReport() { assertHostDeclarations(mdmHost.UUID, []*fleet.MDMAppleHostDeclaration{ {Identifier: "I1", Status: &fleet.MDMDeliveryVerifying, OperationType: fleet.MDMOperationTypeInstall}, {Identifier: "I2", Status: &fleet.MDMDeliveryVerifying, OperationType: fleet.MDMOperationTypeInstall}, + {Identifier: "I3", Status: &fleet.MDMDeliveryVerifying, OperationType: fleet.MDMOperationTypeInstall}, }) // host sends a partial DDM report @@ -1030,6 +1035,7 @@ func (s *integrationMDMTestSuite) TestAppleDDMStatusReport() { assertHostDeclarations(mdmHost.UUID, []*fleet.MDMAppleHostDeclaration{ {Identifier: "I1", Status: &fleet.MDMDeliveryVerified, OperationType: fleet.MDMOperationTypeInstall}, {Identifier: "I2", Status: &fleet.MDMDeliveryVerifying, OperationType: fleet.MDMOperationTypeInstall}, + {Identifier: "I3", Status: &fleet.MDMDeliveryVerifying, OperationType: fleet.MDMOperationTypeInstall}, }) // host sends a report with a wrong (could be old) server token for I2, nothing changes @@ -1042,6 +1048,7 @@ func (s *integrationMDMTestSuite) TestAppleDDMStatusReport() { assertHostDeclarations(mdmHost.UUID, []*fleet.MDMAppleHostDeclaration{ {Identifier: "I1", Status: &fleet.MDMDeliveryVerified, OperationType: fleet.MDMOperationTypeInstall}, {Identifier: "I2", Status: &fleet.MDMDeliveryVerifying, OperationType: fleet.MDMOperationTypeInstall}, + {Identifier: "I3", Status: &fleet.MDMDeliveryVerifying, OperationType: fleet.MDMOperationTypeInstall}, }) // host sends a full report, declaration I2 is invalid @@ -1049,12 +1056,18 @@ func (s *integrationMDMTestSuite) TestAppleDDMStatusReport() { 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}, + {Active: false, Valid: fleet.MDMAppleDeclarationUnknown, Identifier: "I3", ServerToken: i3ServerToken, Reasons: []fleet.MDMAppleDDMStatusErrorReason{ + { + Code: "Error.UnknownDeclarationType", + }, + }}, } _, 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}, + {Identifier: "I3", Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeInstall}, }) // do a batch request, this time I2 is deleted