Fail DDM profiles if response is UnknownDeclarationType (#31606)

fixes: #30835 

<img width="763" height="201" alt="image"
src="https://github.com/user-attachments/assets/66345ff7-46bd-4321-86a5-17031ffb2888"
/>


# Checklist for submitter

If some of the following don't apply, delete the relevant line.

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.

## Testing

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually
This commit is contained in:
Magnus Jensen 2025-08-06 14:38:25 +02:00 committed by GitHub
parent 0773f2cc02
commit 20c282f1a5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 32 additions and 5 deletions

View file

@ -0,0 +1 @@
* Mark DDM profiles as failed if response comes back with Unknown Declaration Type error, and improve upload validation for declaration type.

View file

@ -682,8 +682,8 @@ func (r *MDMAppleRawDeclaration) ValidateUserProvided() error {
return NewInvalidArgumentError(r.Type, "Declaration profile cant include status subscription type. To get hosts 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

View file

@ -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
////////////////////////////////////////////////////////////////////////////////

View file

@ -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
}

View file

@ -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