diff --git a/changes/feature_19010-ipad-ios-wipe b/changes/feature_19010-ipad-ios-wipe new file mode 100644 index 0000000000..872132eea9 --- /dev/null +++ b/changes/feature_19010-ipad-ios-wipe @@ -0,0 +1 @@ +* Added support to wipe iOS/iPadOS devices. diff --git a/cmd/fleetctl/mdm.go b/cmd/fleetctl/mdm.go index b21231dd9f..fabd2539b7 100644 --- a/cmd/fleetctl/mdm.go +++ b/cmd/fleetctl/mdm.go @@ -316,7 +316,6 @@ func hostMdmActionSetup(c *cli.Context, hostIdent string, actionType string) (cl if err != nil { var nfe service.NotFoundErr if errors.As(err, &nfe) { - fmt.Println(hostIdent) return nil, nil, errors.New("The host doesn't exist. Please provide a valid host identifier.") } diff --git a/ee/server/service/hosts.go b/ee/server/service/hosts.go index e6833ee5a2..d2f9c49971 100644 --- a/ee/server/service/hosts.go +++ b/ee/server/service/hosts.go @@ -58,6 +58,8 @@ func (svc *Service) LockHost(ctx context.Context, hostID uint) error { // locking validations are based on the platform of the host switch host.FleetPlatform() { + case "ios", "ipados": + return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Can't lock iOS or iPadOS hosts. Use wipe instead.")) case "darwin": if err := svc.VerifyMDMAppleConfigured(ctx); err != nil { if errors.Is(err, fleet.ErrMDMNotConfigured) { @@ -158,7 +160,7 @@ func (svc *Service) UnlockHost(ctx context.Context, hostID uint) (string, error) // locking validations are based on the platform of the host switch host.FleetPlatform() { - case "darwin": + case "darwin", "ios", "ipados": // all good, no need to check if MDM enrolled, will validate later that it // is currently locked. @@ -249,7 +251,7 @@ func (svc *Service) WipeHost(ctx context.Context, hostID uint) error { // uses scripts, not MDM. var requireMDM bool switch host.FleetPlatform() { - case "darwin": + case "darwin", "ios", "ipados": if err := svc.VerifyMDMAppleConfigured(ctx); err != nil { if errors.Is(err, fleet.ErrMDMNotConfigured) { err = fleet.NewInvalidArgumentError("host_id", fleet.AppleMDMNotConfiguredMessage).WithStatus(http.StatusBadRequest) @@ -449,7 +451,7 @@ func (svc *Service) enqueueWipeHostRequest(ctx context.Context, host *fleet.Host } switch wipeStatus.HostFleetPlatform { - case "darwin": + case "darwin", "ios", "ipados": wipeCommandUUID := uuid.NewString() if err := svc.mdmAppleCommander.EraseDevice(ctx, host, wipeCommandUUID); err != nil { return ctxerr.Wrap(ctx, err, "enqueuing wipe request for darwin") diff --git a/server/fleet/scripts.go b/server/fleet/scripts.go index 6f803f28c1..53b34a1387 100644 --- a/server/fleet/scripts.go +++ b/server/fleet/scripts.go @@ -297,8 +297,10 @@ const ( ) // anchored, so that it matches to the end of the line -var scriptHashbangValidation = regexp.MustCompile(`^#!\s*(:?/usr)?/bin/z?sh(?:\s*|\s+.*)$`) -var ErrUnsupportedInterpreter = errors.New(`Interpreter not supported. Shell scripts must run in "#!/bin/sh" or "#!/bin/zsh."`) +var ( + scriptHashbangValidation = regexp.MustCompile(`^#!\s*(:?/usr)?/bin/z?sh(?:\s*|\s+.*)$`) + ErrUnsupportedInterpreter = errors.New(`Interpreter not supported. Shell scripts must run in "#!/bin/sh" or "#!/bin/zsh."`) +) // ValidateShebang validates if we support a script, and whether we // can execute it directly, or need to pass it to a shell interpreter. @@ -396,7 +398,7 @@ type HostLockWipeStatus struct { } func (s *HostLockWipeStatus) IsPendingLock() bool { - if s.HostFleetPlatform == "darwin" { + if s.HostFleetPlatform == "darwin" || s.HostFleetPlatform == "ios" || s.HostFleetPlatform == "ipados" { // pending lock if an MDM command is queued but no result received yet return s.LockMDMCommand != nil && s.LockMDMCommandResult == nil } @@ -405,7 +407,7 @@ func (s *HostLockWipeStatus) IsPendingLock() bool { } func (s HostLockWipeStatus) IsPendingUnlock() bool { - if s.HostFleetPlatform == "darwin" { + if s.HostFleetPlatform == "darwin" || s.HostFleetPlatform == "ios" || s.HostFleetPlatform == "ipados" { // pending unlock if an unlock was requested return !s.UnlockRequestedAt.IsZero() } @@ -426,7 +428,7 @@ func (s HostLockWipeStatus) IsLocked() bool { // this state is regardless of pending unlock/wipe (it reports whether the // host is locked *now*). - if s.HostFleetPlatform == "darwin" { + if s.HostFleetPlatform == "darwin" || s.HostFleetPlatform == "ios" || s.HostFleetPlatform == "ipados" { // locked if an MDM command was sent and succeeded return s.LockMDMCommand != nil && s.LockMDMCommandResult != nil && s.LockMDMCommandResult.Status == MDMAppleStatusAcknowledged @@ -452,7 +454,7 @@ func (s HostLockWipeStatus) IsWiped() bool { // wiped if an MDM command was sent and succeeded return s.WipeMDMCommand != nil && s.WipeMDMCommandResult != nil && strings.HasPrefix(s.WipeMDMCommandResult.Status, "2") - case "darwin": + case "darwin", "ios", "ipados": // wiped if an MDM command was sent and succeeded return s.WipeMDMCommand != nil && s.WipeMDMCommandResult != nil && s.WipeMDMCommandResult.Status == MDMAppleStatusAcknowledged diff --git a/server/service/apple_mdm.go b/server/service/apple_mdm.go index 9799f6e7e0..23f8d5d673 100644 --- a/server/service/apple_mdm.go +++ b/server/service/apple_mdm.go @@ -1423,6 +1423,12 @@ func (svc *Service) EnqueueMDMAppleCommandRemoveEnrollmentProfile(ctx context.Co return ctxerr.Wrap(ctx, err, "getting host info for mdm apple remove profile command") } + if h.Platform == "ios" || h.Platform == "ipados" { + return &fleet.BadRequestError{ + Message: "Can't turn off MDM for iOS or iPadOS hosts. Use wipe instead.", + } + } + info, err := svc.ds.GetHostMDMCheckinInfo(ctx, h.UUID) if err != nil { return ctxerr.Wrap(ctx, err, "getting mdm checkin info for mdm apple remove profile command") diff --git a/tools/mdm/apple/applebmapi/main.go b/tools/mdm/apple/applebmapi/main.go index ae47ee1902..d1ecd69ed0 100644 --- a/tools/mdm/apple/applebmapi/main.go +++ b/tools/mdm/apple/applebmapi/main.go @@ -19,31 +19,25 @@ import ( "github.com/fleetdm/fleet/v4/server/config" "github.com/fleetdm/fleet/v4/server/datastore/mysql" apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple" - nanodep_client "github.com/fleetdm/fleet/v4/server/mdm/nanodep/client" "github.com/fleetdm/fleet/v4/server/mdm/nanodep/godep" kitlog "github.com/go-kit/kit/log" ) func main() { mysqlAddr := flag.String("mysql", "localhost:3306", "mysql address") - appleBMToken := flag.String("apple-bm-token", "", "path to (decrypted) Apple BM token") + serverPrivateKey := flag.String("server-private-key", "", "fleet server's private key (to decrypt MDM assets)") profileUUID := flag.String("profile-uuid", "", "the Apple profile UUID to retrieve") serialNum := flag.String("serial-number", "", "serial number of a device to get the device details") flag.Parse() - if *appleBMToken == "" { - log.Fatal("must provide Apple BM token") + if *serverPrivateKey == "" { + log.Fatal("must provide -server-private-key") } if *profileUUID != "" && *serialNum != "" { log.Fatal("only one of -profile-uuid or -serial-number must be provided") } - tok, err := os.ReadFile(*appleBMToken) - if err != nil { - log.Fatal(err) - } - cfg := config.MysqlConfig{ Protocol: "tcp", Address: *mysqlAddr, @@ -55,17 +49,19 @@ func main() { ConnMaxLifetime: 0, } logger := kitlog.NewLogfmtLogger(os.Stderr) - opts := []mysql.DBOption{mysql.Logger(logger)} + opts := []mysql.DBOption{ + mysql.Logger(logger), + mysql.WithFleetConfig(&config.FleetConfig{ + Server: config.ServerConfig{ + PrivateKey: *serverPrivateKey, + }, + }), + } mds, err := mysql.New(cfg, clock.C, opts...) if err != nil { log.Fatal(err) } - var jsonTok nanodep_client.OAuth1Tokens - if err := json.Unmarshal(tok, &jsonTok); err != nil { - log.Fatal(err) - } - depStorage, err := mds.NewMDMAppleDEPStorage() if err != nil { log.Fatal(err)