From a33bd6527249eb40e2033b68b6086a58d7eeaf34 Mon Sep 17 00:00:00 2001 From: Sarah Gillespie <73313222+gillespi314@users.noreply.github.com> Date: Wed, 31 Jul 2024 11:34:01 -0500 Subject: [PATCH] Revert "Add minimum os version requirements to DEP enrollment flow" (#20878) --- changes/19674-dep-min-os-version | 1 - server/datastore/mysql/apple_mdm.go | 58 ------ server/datastore/mysql/apple_mdm_test.go | 172 ------------------ server/fleet/apple_mdm.go | 50 ------ server/fleet/datastore.go | 4 - server/fleet/service.go | 3 - server/mdm/apple/AppleIncRootCertificate.cer | Bin 1215 -> 0 bytes server/mdm/apple/deviceinfo.go | 179 ------------------- server/mock/datastore_mock.go | 12 -- server/service/apple_mdm.go | 110 ------------ server/service/apple_mdm_test.go | 78 -------- 11 files changed, 667 deletions(-) delete mode 100644 changes/19674-dep-min-os-version delete mode 100644 server/mdm/apple/AppleIncRootCertificate.cer delete mode 100644 server/mdm/apple/deviceinfo.go diff --git a/changes/19674-dep-min-os-version b/changes/19674-dep-min-os-version deleted file mode 100644 index b9adefe9ec..0000000000 --- a/changes/19674-dep-min-os-version +++ /dev/null @@ -1 +0,0 @@ -- Updated MDM features to enforce minimum OS version settings during Apple Automated Device Enrollment (ADE). diff --git a/server/datastore/mysql/apple_mdm.go b/server/datastore/mysql/apple_mdm.go index 0bb49993b6..a0e95f7d75 100644 --- a/server/datastore/mysql/apple_mdm.go +++ b/server/datastore/mysql/apple_mdm.go @@ -4603,61 +4603,3 @@ LIMIT 500 return deviceUUIDs, nil } - -func (ds *Datastore) GetMDMAppleOSUpdatesSettingsByHostSerial(ctx context.Context, serial string) (*fleet.AppleOSUpdateSettings, error) { - stmt := ` -SELECT - team_id, platform -FROM - hosts h -JOIN - host_dep_assignments hdep ON h.id = host_id -WHERE - hardware_serial = ? AND deleted_at IS NULL -LIMIT 1` - - var dest struct { - TeamID *uint `db:"team_id"` - Platform string `db:"platform"` - } - if err := sqlx.GetContext(ctx, ds.reader(ctx), &dest, stmt, serial); err != nil { - return nil, ctxerr.Wrap(ctx, err, "getting team id for host") - } - - var settings fleet.AppleOSUpdateSettings - if dest.TeamID == nil { - // use the global settings - ac, err := ds.AppConfig(ctx) - if err != nil { - return nil, ctxerr.Wrap(ctx, err, "getting app config for os update settings") - } - switch dest.Platform { - case "ios": - settings = ac.MDM.IOSUpdates - case "ipados": - settings = ac.MDM.IPadOSUpdates - case "darwin": - settings = ac.MDM.MacOSUpdates - default: - return nil, ctxerr.New(ctx, fmt.Sprintf("unsupported platform %s", dest.Platform)) - } - } else { - // use the team settings - tm, err := ds.TeamWithoutExtras(ctx, *dest.TeamID) - if err != nil { - return nil, ctxerr.Wrap(ctx, err, "getting team os update settings") - } - switch dest.Platform { - case "ios": - settings = tm.Config.MDM.IOSUpdates - case "ipados": - settings = tm.Config.MDM.IPadOSUpdates - case "darwin": - settings = tm.Config.MDM.MacOSUpdates - default: - return nil, ctxerr.New(ctx, fmt.Sprintf("unsupported platform %s", dest.Platform)) - } - } - - return &settings, nil -} diff --git a/server/datastore/mysql/apple_mdm_test.go b/server/datastore/mysql/apple_mdm_test.go index 53c8423809..35a8a87497 100644 --- a/server/datastore/mysql/apple_mdm_test.go +++ b/server/datastore/mysql/apple_mdm_test.go @@ -6001,7 +6001,6 @@ func testGetHostUUIDsWithPendingMDMAppleCommands(t *testing.T, ds *Datastore) { require.NoError(t, err) require.ElementsMatch(t, []string{hosts[1].UUID, hosts[2].UUID}, uuids) } - func testHostDetailsMDMProfilesIOSIPadOS(t *testing.T, ds *Datastore) { ctx := context.Background() @@ -6144,174 +6143,3 @@ func testHostDetailsMDMProfilesIOSIPadOS(t *testing.T, ds *Datastore) { require.Equal(t, fleet.MDMDeliveryVerified, *gotProfs[0].Status) } } - -func TestGetMDMAppleOSUpdatesSettingsByHostSerial(t *testing.T) { - ds := CreateMySQLDS(t) - defer ds.Close() - - keys := []string{"ios", "ipados", "macos"} - devicesByKey := map[string]godep.Device{ - "ios": {SerialNumber: "dep-serial-ios-updates", DeviceFamily: "iPhone"}, - "ipados": {SerialNumber: "dep-serial-ipados-updates", DeviceFamily: "iPad"}, - "macos": {SerialNumber: "dep-serial-macos-updates", DeviceFamily: "Mac"}, - } - - getConfigSettings := func(teamID uint, key string) *fleet.AppleOSUpdateSettings { - var settings fleet.AppleOSUpdateSettings - ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { - stmt := fmt.Sprintf(`SELECT json_value->'$.mdm.%s_updates' FROM app_config_json`, key) - if teamID > 0 { - stmt = fmt.Sprintf(`SELECT config->'$.mdm.%s_updates' FROM teams WHERE id = %d`, key, teamID) - } - var raw json.RawMessage - if err := sqlx.GetContext(context.Background(), q, &raw, stmt); err != nil { - return err - } - if err := json.Unmarshal(raw, &settings); err != nil { - return err - } - return nil - }) - return &settings - } - - setConfigSettings := func(teamID uint, key string, minVersion string) { - var mv *string - if minVersion != "" { - mv = &minVersion - } - ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { - stmt := fmt.Sprintf(`UPDATE app_config_json SET json_value = JSON_SET(json_value, '$.mdm.%s_updates.minimum_version', ?)`, key) - if teamID > 0 { - stmt = fmt.Sprintf(`UPDATE teams SET config = JSON_SET(config, '$.mdm.%s_updates.minimum_version', ?) WHERE id = %d`, key, teamID) - } - if _, err := q.ExecContext(context.Background(), stmt, mv); err != nil { - return err - } - return nil - }) - } - - checkExpectedVersion := func(t *testing.T, gotSettings *fleet.AppleOSUpdateSettings, expectedVersion string) { - if expectedVersion == "" { - require.True(t, gotSettings.MinimumVersion.Set) - require.False(t, gotSettings.MinimumVersion.Valid) - require.Empty(t, gotSettings.MinimumVersion.Value) - } else { - require.True(t, gotSettings.MinimumVersion.Set) - require.True(t, gotSettings.MinimumVersion.Valid) - require.Equal(t, expectedVersion, gotSettings.MinimumVersion.Value) - } - } - - checkDevice := func(t *testing.T, teamID uint, key string, wantVersion string) { - checkExpectedVersion(t, getConfigSettings(teamID, key), wantVersion) - gotSettings, err := ds.GetMDMAppleOSUpdatesSettingsByHostSerial(context.Background(), devicesByKey[key].SerialNumber) - require.NoError(t, err) - checkExpectedVersion(t, gotSettings, wantVersion) - } - - // empty global settings to start - for _, key := range keys { - checkExpectedVersion(t, getConfigSettings(0, key), "") - } - - // ingest some test devices - n, _, err := ds.IngestMDMAppleDevicesFromDEPSync(context.Background(), []godep.Device{devicesByKey["ios"], devicesByKey["ipados"], devicesByKey["macos"]}) - require.NoError(t, err) - require.Equal(t, int64(3), n) - hostIDsByKey := map[string]uint{} - for key, device := range devicesByKey { - ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { - var hid uint - err = sqlx.GetContext(context.Background(), q, &hid, "SELECT id FROM hosts WHERE hardware_serial = ?", device.SerialNumber) - require.NoError(t, err) - hostIDsByKey[key] = hid - return nil - }) - } - - // not set in global config, so devics should return empty - checkDevice(t, 0, "ios", "") - checkDevice(t, 0, "ipados", "") - checkDevice(t, 0, "macos", "") - - // set the minimum version for ios - setConfigSettings(0, "ios", "17.1") - checkDevice(t, 0, "ios", "17.1") - checkDevice(t, 0, "ipados", "") // no change - checkDevice(t, 0, "macos", "") // no change - - // set the minimum version for ipados - setConfigSettings(0, "ipados", "17.2") - checkDevice(t, 0, "ios", "17.1") // no change - checkDevice(t, 0, "ipados", "17.2") - checkDevice(t, 0, "macos", "") // no change - - // set the minimum version for macos - setConfigSettings(0, "macos", "14.5") - checkDevice(t, 0, "ios", "17.1") // no change - checkDevice(t, 0, "ipados", "17.2") // no change - checkDevice(t, 0, "macos", "14.5") - - // create a team - team, err := ds.NewTeam(context.Background(), &fleet.Team{Name: "team1"}) - require.NoError(t, err) - - // empty team settings to start - for _, key := range keys { - checkExpectedVersion(t, getConfigSettings(team.ID, key), "") - } - - // transfer ios and ipados to the team - err = ds.AddHostsToTeam(context.Background(), &team.ID, []uint{hostIDsByKey["ios"], hostIDsByKey["ipados"]}) - require.NoError(t, err) - - checkDevice(t, team.ID, "ios", "") // team settings are empty to start - checkDevice(t, team.ID, "ipados", "") // team settings are empty to start - checkDevice(t, 0, "macos", "14.5") // no change, still global - - setConfigSettings(team.ID, "ios", "17.3") - checkDevice(t, team.ID, "ios", "17.3") // team settings are set for ios - checkDevice(t, team.ID, "ipados", "") // team settings are empty for ipados - checkDevice(t, 0, "macos", "14.5") // no change, still global - - setConfigSettings(team.ID, "ipados", "17.4") - checkDevice(t, team.ID, "ios", "17.3") // no change in team settings for ios - checkDevice(t, team.ID, "ipados", "17.4") // team settings are set for ipados - checkDevice(t, 0, "macos", "14.5") // no change, still global - - // transfer macos to the team - err = ds.AddHostsToTeam(context.Background(), &team.ID, []uint{hostIDsByKey["macos"]}) - require.NoError(t, err) - checkDevice(t, team.ID, "macos", "") // team settings are empty for macos - - setConfigSettings(team.ID, "macos", "14.6") - checkDevice(t, team.ID, "macos", "14.6") // team settings are set for macos - - // create a non-DEP host - _, err = ds.NewHost(context.Background(), &fleet.Host{ - DetailUpdatedAt: time.Now(), - LabelUpdatedAt: time.Now(), - PolicyUpdatedAt: time.Now(), - SeenTime: time.Now(), - OsqueryHostID: ptr.String("non-dep-osquery-id"), - NodeKey: ptr.String("non-dep-node-key"), - UUID: "non-dep-uuid", - Hostname: "non-dep-hostname", - Platform: "macos", - HardwareSerial: "non-dep-serial", - }) - - // non-DEP host should return not found - _, err = ds.GetMDMAppleOSUpdatesSettingsByHostSerial(context.Background(), "non-dep-serial") - require.ErrorIs(t, err, sql.ErrNoRows) - - // deleted DEP host should return not found - ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { - _, err := q.ExecContext(context.Background(), "UPDATE host_dep_assignments SET deleted_at = NOW() WHERE host_id = ?", hostIDsByKey["macos"]) - return err - }) - _, err = ds.GetMDMAppleOSUpdatesSettingsByHostSerial(context.Background(), devicesByKey["macos"].SerialNumber) - require.ErrorIs(t, err, sql.ErrNoRows) -} diff --git a/server/fleet/apple_mdm.go b/server/fleet/apple_mdm.go index b86434ac8e..e1cabf2533 100644 --- a/server/fleet/apple_mdm.go +++ b/server/fleet/apple_mdm.go @@ -841,53 +841,3 @@ type MDMAppleDDMActivation struct { ServerToken string `json:"ServerToken"` Type string `json:"Type"` // "com.apple.activation.simple" } - -// MDMAppleMachineInfo is a [device's information][1] sent as part of an MDM enrollment profile request -// -// [1]: https://developer.apple.com/documentation/devicemanagement/machineinfo -type MDMAppleMachineInfo struct { - IMEI string `plist:"IMEI,omitempty"` - Language string `plist:"LANGUAGE,omitempty"` - MDMCanRequestSoftwareUpdate bool `plist:"MDM_CAN_REQUEST_SOFTWARE_UPDATE"` - MEID string `plist:"MEID,omitempty"` - OSVersion string `plist:"OS_VERSION"` - PairingToken string `plist:"PAIRING_TOKEN,omitempty"` - Product string `plist:"PRODUCT"` - Serial string `plist:"SERIAL"` - SoftwareUpdateDeviceID string `plist:"SOFTWARE_UPDATE_DEVICE_ID,omitempty"` - SupplementalBuildVersion string `plist:"SUPPLEMENTAL_BUILD_VERSION,omitempty"` - SupplementalOSVersionExtra string `plist:"SUPPLEMENTAL_OS_VERSION_EXTRA,omitempty"` - UDID string `plist:"UDID"` - Version string `plist:"VERSION"` -} - -// MDMAppleSoftwareUpdateRequiredCode is the [code][1] specified by Apple to indicate that the device -// needs to perform a software update before enrollment and setup can proceed. -// -// [1]: https://developer.apple.com/documentation/devicemanagement/errorcodesoftwareupdaterequired -const MDMAppleSoftwareUpdateRequiredCode = "com.apple.softwareupdate.required" - -// MDMAppleSoftwareUpdateRequiredDetails is the [details][1] specified by Apple for the -// required software update. -// -// [1]: https://developer.apple.com/documentation/devicemanagement/errorcodesoftwareupdaterequired/details -type MDMAppleSoftwareUpdateRequiredDetails struct { - OSVersion string `json:"OSVersion"` - BuildVersion string `json:"BuildVersion"` -} - -// MDMAppleSoftwareUpdateRequired is the [error response][1] specified by Apple to indicate that the device -// needs to perform a software update before enrollment and setup can proceed. -// -// [1]: https://developer.apple.com/documentation/devicemanagement/errorcodesoftwareupdaterequired -type MDMAppleSoftwareUpdateRequired struct { - Code string `json:"code"` // "com.apple.softwareupdate.required" - Details MDMAppleSoftwareUpdateRequiredDetails `json:"details"` -} - -func NewMDMAppleSoftwareUpdateRequired(settings AppleOSUpdateSettings) *MDMAppleSoftwareUpdateRequired { - return &MDMAppleSoftwareUpdateRequired{ - Code: MDMAppleSoftwareUpdateRequiredCode, - Details: MDMAppleSoftwareUpdateRequiredDetails{OSVersion: settings.MinimumVersion.Value}, - } -} diff --git a/server/fleet/datastore.go b/server/fleet/datastore.go index cb85d7782e..a043eba04e 100644 --- a/server/fleet/datastore.go +++ b/server/fleet/datastore.go @@ -1288,10 +1288,6 @@ type Datastore interface { // the provided value. MDMAppleSetPendingDeclarationsAs(ctx context.Context, hostUUID string, status *MDMDeliveryStatus, detail string) error - // GetMDMAppleOSUpdatesSettingsByHostSerial returns applicable Apple OS update settings (if any) - // for the host with the given serial number. The host must be DEP assigned to Fleet. - GetMDMAppleOSUpdatesSettingsByHostSerial(ctx context.Context, hostSerial string) (*AppleOSUpdateSettings, error) - // InsertMDMConfigAssets inserts MDM related config assets, such as SCEP and APNS certs and keys. InsertMDMConfigAssets(ctx context.Context, assets []MDMConfigAsset) error diff --git a/server/fleet/service.go b/server/fleet/service.go index be8cf36adc..5375b3d4bd 100644 --- a/server/fleet/service.go +++ b/server/fleet/service.go @@ -918,9 +918,6 @@ type Service interface { GetMDMManualEnrollmentProfile(ctx context.Context) ([]byte, error) - // CheckMDMAppleEnrollmentWithMinimumOSVersion checks if the minimum OS version is met for a MDM enrollment - CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx context.Context, m *MDMAppleMachineInfo) (*MDMAppleSoftwareUpdateRequired, error) - /////////////////////////////////////////////////////////////////////////////// // CronSchedulesService diff --git a/server/mdm/apple/AppleIncRootCertificate.cer b/server/mdm/apple/AppleIncRootCertificate.cer deleted file mode 100644 index 8a9ff247419dd22a07c837ba7394aeabdd0f14e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1215 zcmXqLV%crb#JqR`GZP~d5E<~YacZ@Bw0-AgWMpM!Fi0}wHsEAq4rO5zW(o~96gCh9 zakzxJ9199^QWZS&lJyML3{*gZ+`_UDLFd$>lFYQsBg2!4D>>yS-j;I@c+L7YuChh5U7`KHvAiEsOqE5wMI&W5Px=0B&b;#hyADPKr1x`dQTTp(jgCTo z!8UtFgP!fq=lSQ_e%AKXkUH`2+}53ZH{)ckownU-we|}?AHyW>jf!G=C0A{DZzqYZ zUR*fIJvj8>dVR;uKYl+hIQwj|k87R0Pjj_t->jT;Rj-bAq&^<-@B zm%W!-{69S|b&uzbviZg$sSC@eoYZAvW@KPo+{9P~43RPeK43h`@-s62XJG-R8#V)e z5MLO?XEk63QUIB4HrbfL%co zBPgB8DzG#$asX{)0b&Md!c0zKWi)8~WT3^yq0I(NqwGwKVsaTJB?ZM+`ugSN<$8&r zl&P1TpQ{gMB`4||G#-X4W-@5pCe^q(C^aWDF)uk)0hmHdGBS%5lHrLqRUxTTAu+E~ zp&+rS1js5bF3n9XR!B@vPAw>b=t%?WNd@6N1&|%Uq@D!K48=g%l*FPGg_6{wT%d-$ z6ouscyp&8(HYirePg5u@PSruNs30Gx7i1YwCER{crYR^&OfJa;IuB@ONosCtUP-YY za{2^jO7!e*{cX?eJDxY@8r; zW8atJ+3zl;@Sm>qH@UIM?q|jS>=W#7YAu_)gB31Y9ND;kmOoeaf9*e!%UL;V#2vx} zUUwk!R<>!CBEM2a=7;zgw~E zguTASugG_6SFxo3)|+Pa2irq$E}yy6$m#cutA+FG76xsX-aFYzMMzw9>OIdRD+ Xyc@&=R&`yy_2kb5PImJRrKO4hMS!&; diff --git a/server/mdm/apple/deviceinfo.go b/server/mdm/apple/deviceinfo.go deleted file mode 100644 index ebed273690..0000000000 --- a/server/mdm/apple/deviceinfo.go +++ /dev/null @@ -1,179 +0,0 @@ -// The contents of this file have been copied and modified pursuant to the following -// license from the original source: -// https://github.com/korylprince/dep-webview-oidc/blob/2dd846a54fed04c16dd227b8c6c31665b4d0ebd8/header/header.go -// -// MIT License -// -// Copyright (c) 2023 Kory Prince -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package apple_mdm - -import ( - "bytes" - "crypto" - "crypto/rsa" - "crypto/sha1" // nolint:gosec // See comments regarding Apple's Root CA below - "crypto/x509" - _ "embed" - "encoding/base64" - "errors" - "fmt" - - "github.com/groob/plist" - "go.mozilla.org/pkcs7" -) - -const DeviceInfoHeader = "x-apple-aspen-deviceinfo" - -// appleRootCert is https://www.apple.com/appleca/AppleIncRootCertificate.cer -// -//go:embed AppleIncRootCertificate.cer -var appleRootCert []byte - -func newAppleRootCert() *x509.Certificate { - cert, err := x509.ParseCertificate(appleRootCert) - if err != nil { - panic(fmt.Errorf("could not parse cert: %w", err)) - } - return cert -} - -// appleRootCA is Apple's Root CA parsed to an *x509.Certificate -var appleRootCA = newAppleRootCert() - -// MachineInfo is a [device's information] sent as part of an MDM enrollment profile request -// -// [device's information]: https://developer.apple.com/documentation/devicemanagement/machineinfo -type MachineInfo struct { - IMEI string `plist:"IMEI,omitempty"` - Language string `plist:"LANGUAGE,omitempty"` - MDMCanRequestSoftwareUpdate bool `plist:"MDM_CAN_REQUEST_SOFTWARE_UPDATE"` - MEID string `plist:"MEID,omitempty"` - OSVersion string `plist:"OS_VERSION"` - PairingToken string `plist:"PAIRING_TOKEN,omitempty"` - Product string `plist:"PRODUCT"` - Serial string `plist:"SERIAL"` - SoftwareUpdateDeviceID string `plist:"SOFTWARE_UPDATE_DEVICE_ID,omitempty"` - SupplementalBuildVersion string `plist:"SUPPLEMENTAL_BUILD_VERSION,omitempty"` - SupplementalOSVersionExtra string `plist:"SUPPLEMENTAL_OS_VERSION_EXTRA,omitempty"` - UDID string `plist:"UDID"` - Version string `plist:"VERSION"` -} - -// verifyPKCS7SHA1RSA performs a manual SHA1withRSA verification, since it's deprecated in Go 1.18. -// If verifyChain is true, the signer certificate and its chain of certificates is verified against Apple's Root CA. -// Also note that the certificate validity time window of the signing cert is not checked, since the cert is expired. -// This follows guidance from Apple on the expired certificate. -func verifyPKCS7SHA1RSA(p7 *pkcs7.PKCS7, verifyChain bool) error { - if len(p7.Signers) == 0 { - return errors.New("not signed") - } - - // get signing cert - issuer := p7.Signers[0].IssuerAndSerialNumber - var signer *x509.Certificate - for _, cert := range p7.Certificates { - if bytes.Equal(cert.RawIssuer, issuer.IssuerName.FullBytes) && cert.SerialNumber.Cmp(issuer.SerialNumber) == 0 { - signer = cert - } - } - - // get sha1 hash of content - hashed := sha1.Sum(p7.Content) // nolint:gosec - - // verify content signature - signature := p7.Signers[0].EncryptedDigest - if err := rsa.VerifyPKCS1v15(signer.PublicKey.(*rsa.PublicKey), crypto.SHA1, hashed[:], signature); err != nil { - return fmt.Errorf("signature could not be verified: %w", err) - } - - if !verifyChain { - return nil - } - - // verify chain from signer to root - cert := signer -outer: - for { - // check if cert is signed by root - if bytes.Equal(cert.RawIssuer, appleRootCA.RawSubject) { - hashed := sha1.Sum(cert.RawTBSCertificate) // nolint:gosec - // check signature - if err := rsa.VerifyPKCS1v15(appleRootCA.PublicKey.(*rsa.PublicKey), crypto.SHA1, hashed[:], cert.Signature); err != nil { - return fmt.Errorf("could not verify root CA signature: %w", err) - } - return nil - } - for _, c := range p7.Certificates { - if cert == c { - continue - } - // check if cert is signed by intermediate cert in chain - if bytes.Equal(cert.RawIssuer, c.RawSubject) { - // check signature - hashed := sha1.Sum(cert.RawTBSCertificate) // nolint:gosec - if err := rsa.VerifyPKCS1v15(c.PublicKey.(*rsa.PublicKey), crypto.SHA1, hashed[:], cert.Signature); err != nil { - return fmt.Errorf("could not verify chained certificate signature: %w", err) - } - cert = c - continue outer - } - } - return errors.New("certificate root not found") - } -} - -// ParseDeviceinfo attempts to parse the provided string, assuming it to be the base64-encoded value -// of an x-apple-aspen-deviceinfo header. If successful, it returns the parsed *MachineInfo. If the -// verify parameter is specified as true, the signature is also verified against Apple's Root CA and -// an error will be returned if the signature is invalid. -// -// Warning: The information in this header, despite being signed by Apple PKI, shouldn't be trusted -// for device attestation or other security purposes. See the related [documentation] and referenced -// [article] for more information. -// -// [documentation]: https://github.com/korylprince/dep-webview-oidc/blob/2dd846a54fed04c16dd227b8c6c31665b4d0ebd8/docs/Architecture.md#x-apple-aspen-deviceinfo-header -// [article]: https://duo.com/labs/research/mdm-me-maybe -func ParseDeviceinfo(b64 string, verify bool) (*MachineInfo, error) { - buf, err := base64.StdEncoding.DecodeString(b64) - if err != nil { - return nil, fmt.Errorf("could not decode base64: %w", err) - } - - p7, err := pkcs7.Parse(buf) - if err != nil { - return nil, fmt.Errorf("could not decode pkcs7: %w", err) - } - - // verify signature and certificate chain - if verify { - if err = verifyPKCS7SHA1RSA(p7, verify); err != nil { - return nil, fmt.Errorf("could not verify signature: %w", err) - } - } - - info := new(MachineInfo) - if err = plist.Unmarshal(p7.Content, info); err != nil { - return nil, fmt.Errorf("could not decode plist: %w", err) - } - - return info, nil -} diff --git a/server/mock/datastore_mock.go b/server/mock/datastore_mock.go index fdbd7a6238..3fdb56d30b 100644 --- a/server/mock/datastore_mock.go +++ b/server/mock/datastore_mock.go @@ -848,8 +848,6 @@ type MDMAppleStoreDDMStatusReportFunc func(ctx context.Context, hostUUID string, type MDMAppleSetPendingDeclarationsAsFunc func(ctx context.Context, hostUUID string, status *fleet.MDMDeliveryStatus, detail string) error -type GetMDMAppleOSUpdatesSettingsByHostSerialFunc func(ctx context.Context, hostSerial string) (*fleet.AppleOSUpdateSettings, error) - type InsertMDMConfigAssetsFunc func(ctx context.Context, assets []fleet.MDMConfigAsset) error type GetAllMDMConfigAssetsByNameFunc func(ctx context.Context, assetNames []fleet.MDMAssetName) (map[fleet.MDMAssetName]fleet.MDMConfigAsset, error) @@ -2257,9 +2255,6 @@ type DataStore struct { MDMAppleSetPendingDeclarationsAsFunc MDMAppleSetPendingDeclarationsAsFunc MDMAppleSetPendingDeclarationsAsFuncInvoked bool - GetMDMAppleOSUpdatesSettingsByHostSerialFunc GetMDMAppleOSUpdatesSettingsByHostSerialFunc - GetMDMAppleOSUpdatesSettingsByHostSerialFuncInvoked bool - InsertMDMConfigAssetsFunc InsertMDMConfigAssetsFunc InsertMDMConfigAssetsFuncInvoked bool @@ -5407,13 +5402,6 @@ func (s *DataStore) MDMAppleSetPendingDeclarationsAs(ctx context.Context, hostUU return s.MDMAppleSetPendingDeclarationsAsFunc(ctx, hostUUID, status, detail) } -func (s *DataStore) GetMDMAppleOSUpdatesSettingsByHostSerial(ctx context.Context, hostSerial string) (*fleet.AppleOSUpdateSettings, error) { - s.mu.Lock() - s.GetMDMAppleOSUpdatesSettingsByHostSerialFuncInvoked = true - s.mu.Unlock() - return s.GetMDMAppleOSUpdatesSettingsByHostSerialFunc(ctx, hostSerial) -} - func (s *DataStore) InsertMDMConfigAssets(ctx context.Context, assets []fleet.MDMConfigAsset) error { s.mu.Lock() s.InsertMDMConfigAssetsFuncInvoked = true diff --git a/server/service/apple_mdm.go b/server/service/apple_mdm.go index 2118b13670..3f1ef747a3 100644 --- a/server/service/apple_mdm.go +++ b/server/service/apple_mdm.go @@ -18,7 +18,6 @@ import ( "sync" "time" - "github.com/Masterminds/semver" "github.com/docker/go-units" "github.com/fleetdm/fleet/v4/pkg/file" "github.com/fleetdm/fleet/v4/pkg/optjson" @@ -1288,38 +1287,6 @@ func (svc *Service) EnqueueMDMAppleCommand( type mdmAppleEnrollRequest struct { Token string `query:"token"` EnrollmentReference string `query:"enrollment_reference,optional"` - MachineInfo *fleet.MDMAppleMachineInfo -} - -func (mdmAppleEnrollRequest) DecodeRequest(ctx context.Context, r *http.Request) (interface{}, error) { - decoded := mdmAppleEnrollRequest{} - - tok := r.URL.Query().Get("token") - if tok == "" { - return nil, &fleet.BadRequestError{ - Message: "token is required", - } - } - decoded.Token = tok - - er := r.URL.Query().Get("enrollment_reference") - decoded.EnrollmentReference = er - - // Parse the machine info from the request body - di := r.Header.Get("x-apple-aspen-deviceinfo") - if di != "" { - // extract x-apple-aspen-deviceinfo custom header from request - parsed, err := apple_mdm.ParseDeviceinfo(di, true) - if err != nil { - return nil, &fleet.BadRequestError{ - Message: "unable to parse deviceinfo header", - } - } - p := fleet.MDMAppleMachineInfo(*parsed) - decoded.MachineInfo = &p - } - - return &decoded, nil } func (r mdmAppleEnrollResponse) error() error { return r.Err } @@ -1329,20 +1296,9 @@ type mdmAppleEnrollResponse struct { // Profile field is used in hijackRender for the response. Profile []byte - - SoftwareUpdateRequired *fleet.MDMAppleSoftwareUpdateRequired } func (r mdmAppleEnrollResponse) hijackRender(ctx context.Context, w http.ResponseWriter) { - if r.SoftwareUpdateRequired != nil { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusForbidden) - if err := json.NewEncoder(w).Encode(r.SoftwareUpdateRequired); err != nil { - encodeError(ctx, ctxerr.New(ctx, "failed to encode software update required"), w) - } - return - } - w.Header().Set("Content-Length", strconv.FormatInt(int64(len(r.Profile)), 10)) w.Header().Set("Content-Type", "application/x-apple-aspen-config") w.Header().Set("X-Content-Type-Options", "nosniff") @@ -1360,16 +1316,6 @@ func (r mdmAppleEnrollResponse) hijackRender(ctx context.Context, w http.Respons func mdmAppleEnrollEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) { req := request.(*mdmAppleEnrollRequest) - sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, req.MachineInfo) - if err != nil { - return mdmAppleEnrollResponse{Err: err}, nil - } - if sur != nil { - return mdmAppleEnrollResponse{ - SoftwareUpdateRequired: sur, - }, nil - } - profile, err := svc.GetMDMAppleEnrollmentProfileByToken(ctx, req.Token, req.EnrollmentReference) if err != nil { return mdmAppleEnrollResponse{Err: err}, nil @@ -1430,62 +1376,6 @@ func (svc *Service) GetMDMAppleEnrollmentProfileByToken(ctx context.Context, tok return signed, nil } -func (svc *Service) CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx context.Context, m *fleet.MDMAppleMachineInfo) (*fleet.MDMAppleSoftwareUpdateRequired, error) { - // skipauth: The enroll profile endpoint is unauthenticated. - svc.authz.SkipAuthorization(ctx) - - if m == nil { - level.Info(svc.logger).Log("msg", "no machine info, skipping os version check") - return nil, nil - } - - if !m.MDMCanRequestSoftwareUpdate { - level.Info(svc.logger).Log("msg", "mdm cannot request software update, skipping os version check", "machine_info", *m) - return nil, nil - } - - // NOTE: Under the hood, the datastore is joining host_dep_assignments to the hosts table to - // look up DEP hosts by serial number. It grabs the team id and platform from the - // hosts table. Then it uses the team id to get either the global config or team config. - // Finally, it uses the platform to get os updates settings from the config for - // one of ios, ipados, or darwin, as applicable. There's a lot of assumptions going on here, not - // least of which is that the platform is correct in the hosts table. If the platform is wrong, - // we'll end up with a meaningless comparison of unrelated versions. We could potentially add - // some cross-check against the machine info to ensure that the platform of the host aligns with - // what we expect from the machine info. But that would involve work to derive the platform from - // the machine info (presumably from the product name, but that's not a 1:1 mapping). - settings, err := svc.ds.GetMDMAppleOSUpdatesSettingsByHostSerial(ctx, m.Serial) - if err != nil { - if fleet.IsNotFound(err) { - level.Info(svc.logger).Log("msg", "settings not found, skipping os version check", "machine_info", *m) - return nil, nil - } - return nil, ctxerr.Wrap(ctx, err, "get os updates settings") - } - - // TODO: confirm what this check should do - if !settings.MinimumVersion.Set || !settings.MinimumVersion.Valid || settings.MinimumVersion.Value == "" { - level.Info(svc.logger).Log("msg", "settings not set, skipping os version check", "machine_info", *m, "settings", settings) - return nil, nil - } - - want, err := semver.NewVersion(settings.MinimumVersion.Value) - if err != nil { - return nil, ctxerr.Wrap(ctx, err, "parsing minimum version") - } - - got, err := semver.NewVersion(m.OSVersion) - if err != nil { - return nil, ctxerr.Wrap(ctx, err, "parsing device os version") - } - - if got.LessThan(want) { - return fleet.NewMDMAppleSoftwareUpdateRequired(*settings), nil - } - - return nil, nil -} - func (svc *Service) mdmPushCertTopic(ctx context.Context) (string, error) { assets, err := svc.ds.GetAllMDMConfigAssetsByName(ctx, []fleet.MDMAssetName{ fleet.MDMAssetAPNSCert, diff --git a/server/service/apple_mdm_test.go b/server/service/apple_mdm_test.go index 4c959a7649..545de31a0a 100644 --- a/server/service/apple_mdm_test.go +++ b/server/service/apple_mdm_test.go @@ -24,7 +24,6 @@ import ( "testing" "time" - "github.com/fleetdm/fleet/v4/pkg/optjson" "github.com/fleetdm/fleet/v4/server/authz" "github.com/fleetdm/fleet/v4/server/config" "github.com/fleetdm/fleet/v4/server/contexts/license" @@ -3372,80 +3371,3 @@ func TestUnmarshalAppList(t *testing.T) { require.NoError(t, err) assert.ElementsMatch(t, expectedSoftware, software) } - -func TestCheckMDMAppleEnrollmentWithMinimumOSVersion(t *testing.T) { - svc, ctx, ds := setupAppleMDMService(t, &fleet.LicenseInfo{Tier: fleet.TierPremium}) - - ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serialNumber string) (*fleet.AppleOSUpdateSettings, error) { - return &fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("14.2"), - }, nil - } - - testCases := []struct { - name string - deviceOSVersion string - mdmCanRequestSoftwareUpdate bool - wantUpdateRequired string - }{ - { - name: "OS version is greater than minimum", - deviceOSVersion: "15.0", - mdmCanRequestSoftwareUpdate: true, - wantUpdateRequired: "", - }, - { - name: "OS version is equal to minimum", - deviceOSVersion: "14.2", - mdmCanRequestSoftwareUpdate: true, - wantUpdateRequired: "", - }, - { - name: "OS version is less than minimum", - deviceOSVersion: "14.0.2", - mdmCanRequestSoftwareUpdate: true, - wantUpdateRequired: "14.2", - }, - { - name: "OS version is less than minimum but MDM cannot request software update", - deviceOSVersion: "14.0.2", - mdmCanRequestSoftwareUpdate: false, - wantUpdateRequired: "", - }, - } - - for _, tt := range testCases { - t.Run(tt.name, func(t *testing.T) { - sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, &fleet.MDMAppleMachineInfo{OSVersion: tt.deviceOSVersion, MDMCanRequestSoftwareUpdate: tt.mdmCanRequestSoftwareUpdate}) - require.NoError(t, err) - if tt.wantUpdateRequired == "" { - require.Nil(t, sur) - } else { - require.Equal(t, &fleet.MDMAppleSoftwareUpdateRequired{ - Code: fleet.MDMAppleSoftwareUpdateRequiredCode, - Details: fleet.MDMAppleSoftwareUpdateRequiredDetails{ - OSVersion: tt.wantUpdateRequired, - }, - }, sur) - } - }) - } - - t.Run("error getting OS update settings", func(t *testing.T) { - ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serialNumber string) (*fleet.AppleOSUpdateSettings, error) { - return nil, newNotFoundError() - } - - sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, &fleet.MDMAppleMachineInfo{OSVersion: "14.0.2", MDMCanRequestSoftwareUpdate: true}) - require.NoError(t, err) - require.Nil(t, sur) - - ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serialNumber string) (*fleet.AppleOSUpdateSettings, error) { - return nil, errors.New("error") - } - - sur, err = svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, &fleet.MDMAppleMachineInfo{OSVersion: "14.0.2", MDMCanRequestSoftwareUpdate: true}) - require.Error(t, err) - require.Nil(t, sur) - }) -}