mirror of
https://github.com/fleetdm/fleet
synced 2026-05-22 16:39:01 +00:00
Revert "Add minimum os version requirements to DEP enrollment flow" (#20878)
This commit is contained in:
parent
ff623f9875
commit
a33bd65272
11 changed files with 0 additions and 667 deletions
|
|
@ -1 +0,0 @@
|
|||
- Updated MDM features to enforce minimum OS version settings during Apple Automated Device Enrollment (ADE).
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue