Implement support for Wipe for iOS/iPadOS devices (#19704)

Backend changes for #19010.

- [X] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://fleetdm.com/docs/contributing/committing-changes#changes-files)
for more information.
- [X] Manual QA for all new/changed functionality
This commit is contained in:
Lucas Manuel Rodriguez 2024-06-13 12:26:02 -03:00 committed by GitHub
parent a88a25aa00
commit af525223f2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 31 additions and 25 deletions

View file

@ -0,0 +1 @@
* Added support to wipe iOS/iPadOS devices.

View file

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

View file

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

View file

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

View file

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

View file

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