Revert "Add minimum os version requirements to DEP enrollment flow" (#20878)

This commit is contained in:
Sarah Gillespie 2024-07-31 11:34:01 -05:00 committed by GitHub
parent ff623f9875
commit a33bd65272
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 0 additions and 667 deletions

View file

@ -1 +0,0 @@
- Updated MDM features to enforce minimum OS version settings during Apple Automated Device Enrollment (ADE).

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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