Updated FileVault and BitLocker error messages (#27365)

For #24862

# Checklist for submitter

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

<!-- Note that API documentation changes are now addressed by the
product design team. -->

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
- [x] Added/updated automated tests
- [x] A detailed QA plan exists on the associated ticket (if it isn't
there, work with the product group's QA engineer to add it)
- [x] Manual QA for all new/changed functionality
This commit is contained in:
Victor Lyuboslavsky
2025-03-21 09:56:50 -05:00
committed by GitHub
parent bbe3f18b88
commit e7e9f54071
10 changed files with 101 additions and 43 deletions
@@ -0,0 +1 @@
Improved error messages when Fleet admin tries to upload a FileVault(macOS) or a BitLocker(Windows) configuration profile.
+11 -7
View File
@@ -1,6 +1,10 @@
package service
import "text/template"
import (
"text/template"
"github.com/fleetdm/fleet/v4/server/mdm/apple/mobileconfig"
)
type fileVaultProfileOptions struct {
PayloadIdentifier string
@@ -22,9 +26,9 @@ var fileVaultProfileTemplate = template.Must(template.New("").Option("missingkey
<key>PayloadDisplayName</key>
<string>FileVault 2</string>
<key>PayloadIdentifier</key>
<string>com.apple.MCX.FileVault2.3548D750-6357-4910-8DEA-D80ADCE2C787</string>
<string>` + mobileconfig.FleetFileVaultPayloadType + `.3548D750-6357-4910-8DEA-D80ADCE2C787</string>
<key>PayloadType</key>
<string>com.apple.MCX.FileVault2</string>
<string>` + mobileconfig.FleetFileVaultPayloadType + `</string>
<key>PayloadUUID</key>
<string>3548D750-6357-4910-8DEA-D80ADCE2C787</string>
<key>PayloadVersion</key>
@@ -44,9 +48,9 @@ var fileVaultProfileTemplate = template.Must(template.New("").Option("missingkey
<key>PayloadDisplayName</key>
<string>FileVault Recovery Key Escrow</string>
<key>PayloadIdentifier</key>
<string>com.apple.security.FDERecoveryKeyEscrow.3690D771-DCB8-4D5D-97D6-209A138DF03E</string>
<string>` + mobileconfig.FleetRecoveryKeyEscrowPayloadType + `.3690D771-DCB8-4D5D-97D6-209A138DF03E</string>
<key>PayloadType</key>
<string>com.apple.security.FDERecoveryKeyEscrow</string>
<string>` + mobileconfig.FleetRecoveryKeyEscrowPayloadType + `</string>
<key>PayloadUUID</key>
<string>3C329F2B-3D47-4141-A2B5-5C52A2FD74F8</string>
<key>PayloadVersion</key>
@@ -72,9 +76,9 @@ var fileVaultProfileTemplate = template.Must(template.New("").Option("missingkey
<key>dontAllowFDEDisable</key>
<true/>
<key>PayloadIdentifier</key>
<string>com.apple.MCX.62024f29-105E-497A-A724-1D5BA4D9E854</string>
<string>` + mobileconfig.FleetFileVaultOptionsPayloadType + `.62024f29-105E-497A-A724-1D5BA4D9E854</string>
<key>PayloadType</key>
<string>com.apple.MCX</string>
<string>` + mobileconfig.FleetFileVaultOptionsPayloadType + `</string>
<key>PayloadUUID</key>
<string>62024f29-105E-497A-A724-1D5BA4D9E854</string>
<key>PayloadVersion</key>
@@ -51,7 +51,23 @@ export const getErrorMessage = (err: AxiosResponse<IApiError>) => {
) {
return (
<span>
{apiReason} To control these settings, go to <b>Disk encryption</b>.
Couldn&apos;t add. The configuration profile can&apos;t include
BitLocker settings. To control these settings, go to{" "}
<b>Disk encryption</b>.
</span>
);
}
if (
apiReason.includes(
"The configuration profile can't include FileVault settings."
)
) {
return (
<span>
Couldn&apos;t add. The configuration profile can&apos;t include
FileVault settings. To control these settings, go to{" "}
<b>Disk encryption</b>.
</span>
);
}
+4 -4
View File
@@ -114,17 +114,17 @@ func TestMDMAppleConfigProfileScreenPayloadContent(t *testing.T) {
{
testName: "AllFileVaultScreened",
payloadTypes: []string{"com.apple.security.FDERecoveryKeyEscrow", "com.apple.MCX.FileVault2", "com.apple.security.FDERecoveryRedirect"},
shouldFail: []string{"com.apple.security.FDERecoveryKeyEscrow", "com.apple.MCX.FileVault2", "com.apple.security.FDERecoveryRedirect"},
shouldFail: []string{"The configuration profile can't include FileVault settings."},
},
{
testName: "FileVault2Screened",
payloadTypes: []string{"com.apple.MCX.FileVault2"},
shouldFail: []string{"com.apple.MCX.FileVault2"},
shouldFail: []string{"The configuration profile can't include FileVault settings."},
},
{
testName: "FDERecoveryKeyEscrowScreened",
payloadTypes: []string{"com.apple.security.FDERecoveryKeyEscrow"},
shouldFail: []string{"com.apple.security.FDERecoveryKeyEscrow"},
shouldFail: []string{"The configuration profile can't include FileVault settings."},
},
{
testName: "FDERecoveryRedirectScreened",
@@ -139,7 +139,7 @@ func TestMDMAppleConfigProfileScreenPayloadContent(t *testing.T) {
{
testName: "FileVaultMixedWithOtherPayloadTypes",
payloadTypes: []string{"com.apple.MCX.FileVault2", "com.apple.security.firewall", "com.apple.security.FDERecoveryKeyEscrow", "com.apple.MCX"},
shouldFail: []string{"com.apple.MCX.FileVault2", "com.apple.security.FDERecoveryKeyEscrow"},
shouldFail: []string{"The configuration profile can't include FileVault settings."},
},
{
testName: "NoPayloadContent",
+10 -3
View File
@@ -146,8 +146,8 @@ func (m *MDMWindowsConfigProfile) ValidateUserProvided() error {
return nil
}
var fleetProvidedLocURIValidationMap = map[string][2]string{
syncml.FleetBitLockerTargetLocURI: {"BitLocker", "mdm.enable_disk_encryption"},
var fleetProvidedLocURIValidationMap = map[string][]string{
syncml.FleetBitLockerTargetLocURI: nil,
syncml.FleetOSUpdateTargetLocURI: {"Windows updates", "mdm.windows_updates"},
}
@@ -155,7 +155,14 @@ func validateFleetProvidedLocURI(locURI string) error {
sanitizedLocURI := strings.TrimSpace(locURI)
for fleetLocURI, errHints := range fleetProvidedLocURIValidationMap {
if strings.Contains(sanitizedLocURI, fleetLocURI) {
return fmt.Errorf("Custom configuration profiles can't include %s settings. To control these settings, use the %s option.", errHints[0], errHints[1])
if fleetLocURI == syncml.FleetBitLockerTargetLocURI {
return errors.New("The configuration profile can't include BitLocker settings. To control these settings use disk encryption endpoint.")
}
if len(errHints) == 2 {
return fmt.Errorf("Custom configuration profiles can't include %s settings. To control these settings, use the %s option.",
errHints[0], errHints[1])
}
return fmt.Errorf("Custom configuration profiles can't include these settings. %q", errHints)
}
}
+3 -3
View File
@@ -65,7 +65,7 @@ func TestValidateUserProvided(t *testing.T) {
</Replace>
`),
},
wantErr: "Custom configuration profiles can't include BitLocker settings.",
wantErr: "The configuration profile can't include BitLocker settings.",
},
{
name: "Reserved LocURI with implicit ./Device prefix",
@@ -78,7 +78,7 @@ func TestValidateUserProvided(t *testing.T) {
</Replace>
`),
},
wantErr: "Custom configuration profiles can't include BitLocker settings.",
wantErr: "The configuration profile can't include BitLocker settings.",
},
{
name: "XML with Multiple Replace Elements",
@@ -121,7 +121,7 @@ func TestValidateUserProvided(t *testing.T) {
</Replace>
`),
},
wantErr: "Custom configuration profiles can't include BitLocker settings",
wantErr: "The configuration profile can't include BitLocker settings.",
},
{
name: "XML with Mixed Replace and Add",
+15 -8
View File
@@ -18,7 +18,10 @@ import (
const (
// FleetFileVaultPayloadIdentifier is the value for the PayloadIdentifier
// used by Fleet to configure FileVault and FileVault Escrow.
FleetFileVaultPayloadIdentifier = "com.fleetdm.fleet.mdm.filevault"
FleetFileVaultPayloadIdentifier = "com.fleetdm.fleet.mdm.filevault"
FleetFileVaultPayloadType = "com.apple.MCX.FileVault2"
FleetFileVaultOptionsPayloadType = "com.apple.MCX"
FleetRecoveryKeyEscrowPayloadType = "com.apple.security.FDERecoveryKeyEscrow"
// FleetdConfigPayloadIdentifier is the value for the PayloadIdentifier used
// by fleetd to read configuration values from the system.
@@ -58,15 +61,12 @@ func FleetPayloadIdentifiers() map[string]struct{} {
// FleetPayloadTypes returns a map of PayloadType strings
// that are handled and delivered by Fleet.
//
// TODO(roperzh): when I was refactoring this, I noticed that the strings are
// not constants, we should refactor that and use the constant in the templates
// we use to generate the FileVault mobileconfig.
func FleetPayloadTypes() map[string]struct{} {
return map[string]struct{}{
"com.apple.security.FDERecoveryKeyEscrow": {},
"com.apple.MCX.FileVault2": {},
"com.apple.security.FDERecoveryRedirect": {},
FleetRecoveryKeyEscrowPayloadType: {},
FleetFileVaultPayloadType: {},
FleetFileVaultOptionsPayloadType: {},
"com.apple.security.FDERecoveryRedirect": {}, // no longer supported in macOS 10.13 and later
}
}
@@ -249,6 +249,13 @@ func (mc *Mobileconfig) ScreenPayloads() error {
}
if len(screenedTypes) > 0 {
for _, t := range screenedTypes {
switch t {
case FleetFileVaultPayloadType, FleetFileVaultOptionsPayloadType, FleetRecoveryKeyEscrowPayloadType:
return errors.New("Couldn't add. The configuration profile can't include FileVault settings. " +
"To control these settings use disk encryption endpoint.")
}
}
return fmt.Errorf("unsupported PayloadType(s): %s", strings.Join(screenedTypes, ", "))
}
+4 -4
View File
@@ -1792,7 +1792,7 @@ func TestMDMBatchSetAppleProfiles(t *testing.T) {
false,
nil,
nil,
[][]byte{[]byte(`<?xml version="1.0" encoding="UTF-8"?>
[][]byte{[]byte(fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
@@ -1806,7 +1806,7 @@ func TestMDMBatchSetAppleProfiles(t *testing.T) {
<key>PayloadIdentifier</key>
<string>com.apple.MCX.FileVault2.A5874654-D6BA-4649-84B5-43847953B369</string>
<key>PayloadType</key>
<string>com.apple.MCX.FileVault2</string>
<string>%s</string>
<key>PayloadUUID</key>
<string>A5874654-D6BA-4649-84B5-43847953B369</string>
<key>PayloadVersion</key>
@@ -1824,8 +1824,8 @@ func TestMDMBatchSetAppleProfiles(t *testing.T) {
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>`)},
"unsupported PayloadType(s)",
</plist>`, mobileconfig.FleetFileVaultPayloadType))},
"The configuration profile can't include FileVault settings.",
},
}
for name := range fleetmdm.FleetReservedProfileNames() {
@@ -2350,7 +2350,12 @@ func (s *integrationMDMTestSuite) TestBatchSetMDMAppleProfiles() {
mobileconfigForTestWithContent("N1", "I1", "II1", p, ""),
}}, http.StatusUnprocessableEntity, "team_id", fmt.Sprint(tm.ID))
errMsg := extractServerErrorText(res.Body)
require.Contains(t, errMsg, fmt.Sprintf("Validation Failed: unsupported PayloadType(s): %s", p))
switch p {
case mobileconfig.FleetFileVaultPayloadType, mobileconfig.FleetFileVaultOptionsPayloadType, mobileconfig.FleetRecoveryKeyEscrowPayloadType:
assert.Contains(t, errMsg, "Validation Failed: Couldn't add. The configuration profile can't include FileVault settings.")
default:
assert.Contains(t, errMsg, fmt.Sprintf("Validation Failed: unsupported PayloadType(s): %s", p))
}
}
// payloads with reserved identifiers
@@ -3203,8 +3208,10 @@ func (s *integrationMDMTestSuite) TestMDMConfigProfileCRUD() {
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 add. Custom configuration profiles can't include BitLocker settings.")
assertWindowsProfile("updates.xml", syncml.FleetOSUpdateTargetLocURI, testTeam.ID, nil, http.StatusBadRequest, "Couldn't add. Custom configuration profiles can't include Windows updates settings.")
assertWindowsProfile("bitlocker.xml", syncml.FleetBitLockerTargetLocURI, 0, nil, http.StatusBadRequest,
"Couldn't add. The configuration profile can't include BitLocker settings.")
assertWindowsProfile("updates.xml", syncml.FleetOSUpdateTargetLocURI, testTeam.ID, nil, http.StatusBadRequest,
"Couldn't add. Custom configuration profiles can't include Windows updates settings.")
// Fleet-reserved profiles
for name := range servermdm.FleetReservedProfileNames() {
@@ -4304,7 +4311,12 @@ func (s *integrationMDMTestSuite) TestBatchSetMDMProfiles() {
{Name: "N4", Contents: declarationForTest("D1")},
}}, http.StatusUnprocessableEntity, "team_id", fmt.Sprint(tm.ID))
errMsg := extractServerErrorText(res.Body)
require.Contains(t, errMsg, fmt.Sprintf("Validation Failed: unsupported PayloadType(s): %s", p))
switch p {
case mobileconfig.FleetFileVaultPayloadType, mobileconfig.FleetFileVaultOptionsPayloadType, mobileconfig.FleetRecoveryKeyEscrowPayloadType:
assert.Contains(t, errMsg, "Validation Failed: Couldn't add. The configuration profile can't include FileVault settings.")
default:
assert.Contains(t, errMsg, fmt.Sprintf("Validation Failed: unsupported PayloadType(s): %s", p))
}
}
// payloads with reserved identifiers
@@ -4354,7 +4366,7 @@ func (s *integrationMDMTestSuite) TestBatchSetMDMProfiles() {
{Name: "N3", Contents: syncMLForTest("./Foo/Bar")},
}}, http.StatusUnprocessableEntity, "team_id", fmt.Sprint(tm.ID))
errMsg = extractServerErrorText(res.Body)
require.Contains(t, errMsg, "Custom configuration profiles can't include BitLocker settings. To control these settings, use the mdm.enable_disk_encryption option.")
assert.Contains(t, errMsg, "Validation Failed: The configuration profile can't include BitLocker settings.")
// os updates
res = s.Do("POST", "/api/v1/fleet/mdm/profiles/batch", batchSetMDMProfilesRequest{Profiles: []fleet.MDMProfileBatchPayload{
@@ -4578,7 +4590,12 @@ func (s *integrationMDMTestSuite) TestBatchSetMDMProfilesBackwardsCompat() {
"N3": syncMLForTest("./Foo/Bar"),
}}, http.StatusUnprocessableEntity, "team_id", fmt.Sprint(tm.ID))
errMsg := extractServerErrorText(res.Body)
require.Contains(t, errMsg, fmt.Sprintf("Validation Failed: unsupported PayloadType(s): %s", p))
switch p {
case mobileconfig.FleetFileVaultPayloadType, mobileconfig.FleetFileVaultOptionsPayloadType, mobileconfig.FleetRecoveryKeyEscrowPayloadType:
assert.Contains(t, errMsg, "Validation Failed: Couldn't add. The configuration profile can't include FileVault settings.")
default:
assert.Contains(t, errMsg, fmt.Sprintf("Validation Failed: unsupported PayloadType(s): %s", p))
}
}
// payloads with reserved identifiers
@@ -4599,7 +4616,7 @@ func (s *integrationMDMTestSuite) TestBatchSetMDMProfilesBackwardsCompat() {
"N3": syncMLForTest("./Foo/Bar"),
}}, http.StatusUnprocessableEntity, "team_id", fmt.Sprint(tm.ID))
errMsg := extractServerErrorText(res.Body)
require.Contains(t, errMsg, "Custom configuration profiles can't include BitLocker settings. To control these settings, use the mdm.enable_disk_encryption option.")
assert.Contains(t, errMsg, "Validation Failed: The configuration profile can't include BitLocker settings.")
// os updates
res = s.Do("POST", "/api/v1/fleet/mdm/profiles/batch", map[string]any{"profiles": map[string][]byte{
+12 -6
View File
@@ -9,6 +9,7 @@ import (
"crypto/x509/pkix"
"database/sql"
"errors"
"fmt"
"math/big"
"net/http"
"net/http/httptest"
@@ -17,6 +18,7 @@ import (
"testing"
"time"
"github.com/fleetdm/fleet/v4/server/mdm/apple/mobileconfig"
nanodep_client "github.com/fleetdm/fleet/v4/server/mdm/nanodep/client"
nanodep_mock "github.com/fleetdm/fleet/v4/server/mock/nanodep"
"github.com/jmoiron/sqlx"
@@ -1216,7 +1218,9 @@ func TestUploadWindowsMDMConfigProfileValidations(t *testing.T) {
{"duplicate profile name", 0, `<Replace>duplicate</Replace>`, true, "configuration profile with this name already exists"},
{"multiple Replace", 0, `<Replace>a</Replace><Replace>b</Replace>`, true, ""},
{"Replace and non-Replace", 0, `<Replace>a</Replace><Get>b</Get>`, true, "Windows configuration profiles can only have <Replace> or <Add> top level elements."},
{"BitLocker profile", 0, `<Replace><Item><Target><LocURI>./Device/Vendor/MSFT/BitLocker/AllowStandardUserEncryption</LocURI></Target></Item></Replace>`, true, "Custom configuration profiles can't include BitLocker settings."},
{"BitLocker profile", 0,
`<Replace><Item><Target><LocURI>./Device/Vendor/MSFT/BitLocker/AllowStandardUserEncryption</LocURI></Target></Item></Replace>`, true,
"The configuration profile can't include BitLocker settings."},
{"Windows updates profile", 0, `<Replace><Item><Target><LocURI> ./Device/Vendor/MSFT/Policy/Config/Update/ConfigureDeadlineNoAutoRebootForFeatureUpdates </LocURI></Target></Item></Replace>`, true, "Custom configuration profiles can't include Windows updates settings."},
{"unsupported Fleet variable", 0, `<Replace>$FLEET_VAR_BOZO</Replace>`, true, "Fleet variable"},
@@ -1228,7 +1232,9 @@ func TestUploadWindowsMDMConfigProfileValidations(t *testing.T) {
{"team duplicate profile name", 1, `<Replace>duplicate</Replace>`, true, "configuration profile with this name already exists"},
{"team multiple Replace", 1, `<Replace>a</Replace><Replace>b</Replace>`, true, ""},
{"team Replace and non-Replace", 1, `<Replace>a</Replace><Get>b</Get>`, true, "Windows configuration profiles can only have <Replace> or <Add> top level elements."},
{"team BitLocker profile", 1, `<Replace><Item><Target><LocURI>./Device/Vendor/MSFT/BitLocker/AllowStandardUserEncryption</LocURI></Target></Item></Replace>`, true, "Custom configuration profiles can't include BitLocker settings."},
{"team BitLocker profile", 1,
`<Replace><Item><Target><LocURI>./Device/Vendor/MSFT/BitLocker/AllowStandardUserEncryption</LocURI></Target></Item></Replace>`, true,
"The configuration profile can't include BitLocker settings."},
{"team Windows updates profile", 1, `<Replace><Item><Target><LocURI> ./Device/Vendor/MSFT/Policy/Config/Update/ConfigureDeadlineNoAutoRebootForFeatureUpdates </LocURI></Target></Item></Replace>`, true, "Custom configuration profiles can't include Windows updates settings."},
{"invalid team", 2, `<Replace></Replace>`, true, "not found"},
@@ -1541,7 +1547,7 @@ func TestMDMBatchSetProfiles(t *testing.T) {
nil,
[]fleet.MDMProfileBatchPayload{
{
Name: "foo", Contents: []byte(`<?xml version="1.0" encoding="UTF-8"?>
Name: "foo", Contents: []byte(fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
@@ -1555,7 +1561,7 @@ func TestMDMBatchSetProfiles(t *testing.T) {
<key>PayloadIdentifier</key>
<string>com.apple.MCX.FileVault2.A5874654-D6BA-4649-84B5-43847953B369</string>
<key>PayloadType</key>
<string>com.apple.MCX.FileVault2</string>
<string>%s</string>
<key>PayloadUUID</key>
<string>A5874654-D6BA-4649-84B5-43847953B369</string>
<key>PayloadVersion</key>
@@ -1573,10 +1579,10 @@ func TestMDMBatchSetProfiles(t *testing.T) {
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>`),
</plist>`, mobileconfig.FleetFileVaultPayloadType)),
},
},
"unsupported PayloadType(s)",
"The configuration profile can't include FileVault settings.",
},
{
"unsupported Apple config profile Fleet variable",