diff --git a/changes/22773-fma-uninstall-fix b/changes/22773-fma-uninstall-fix new file mode 100644 index 0000000000..74c4390533 --- /dev/null +++ b/changes/22773-fma-uninstall-fix @@ -0,0 +1 @@ +- Fix some cases where Fleet Maintained Apps generated incorrect uninstall scripts diff --git a/server/mdm/maintainedapps/apps.json b/server/mdm/maintainedapps/apps.json index db9a5d0308..ce8eaf816e 100644 --- a/server/mdm/maintainedapps/apps.json +++ b/server/mdm/maintainedapps/apps.json @@ -12,7 +12,15 @@ { "identifier": "box-drive", "bundle_identifier": "com.box.desktop", - "installer_format": "pkg" + "installer_format": "pkg", + "pre_uninstall_scripts": [ + "(cd /Users/$LOGGED_IN_USER; sudo -u $LOGGED_IN_USER fileproviderctl domain remove -A com.box.desktop.boxfileprovider)", + "(cd /Users/$LOGGED_IN_USER; sudo -u $LOGGED_IN_USER /Applications/Box.app/Contents/MacOS/fpe/streem --remove-fpe-domain-and-archive-unsynced-content Box)", + "(cd /Users/$LOGGED_IN_USER; sudo -u $LOGGED_IN_USER /Applications/Box.app/Contents/MacOS/fpe/streem --remove-fpe-domain-and-preserve-unsynced-content Box)", + "(cd /Users/$LOGGED_IN_USER; defaults delete com.box.desktop)", + "echo \"${LOGGED_IN_USER} ALL = (root) NOPASSWD: /Library/Application\\ Support/Box/uninstall_box_drive_r\" >> /etc/sudoers.d/box_uninstall" + ], + "post_uninstall_scripts": ["rm /etc/sudoers.d/box_uninstall"] }, { "identifier": "brave-browser", diff --git a/server/mdm/maintainedapps/ingest.go b/server/mdm/maintainedapps/ingest.go index 11fc619bcf..2c2c86942e 100644 --- a/server/mdm/maintainedapps/ingest.go +++ b/server/mdm/maintainedapps/ingest.go @@ -25,9 +25,11 @@ import ( var appsJSON []byte type maintainedApp struct { - Identifier string `json:"identifier"` - BundleIdentifier string `json:"bundle_identifier"` - InstallerFormat string `json:"installer_format"` + Identifier string `json:"identifier"` + BundleIdentifier string `json:"bundle_identifier"` + InstallerFormat string `json:"installer_format"` + PreUninstallScripts []string `json:"pre_uninstall_scripts"` + PostUninstallScripts []string `json:"post_uninstall_scripts"` } const baseBrewAPIURL = "https://formulae.brew.sh/api/" @@ -164,6 +166,9 @@ func (i ingester) ingestOne(ctx context.Context, app maintainedApp, client *http if err != nil { return ctxerr.Wrapf(ctx, err, "create install script for cask %s", app.Identifier) } + + cask.PreUninstallScripts = app.PreUninstallScripts + cask.PostUninstallScripts = app.PostUninstallScripts uninstallScript := uninstallScriptForApp(&cask) _, err = i.ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{ @@ -182,15 +187,17 @@ func (i ingester) ingestOne(ctx context.Context, app maintainedApp, client *http } type brewCask struct { - Token string `json:"token"` - FullToken string `json:"full_token"` - Tap string `json:"tap"` - Name []string `json:"name"` - Desc string `json:"desc"` - URL string `json:"url"` - Version string `json:"version"` - SHA256 string `json:"sha256"` - Artifacts []*brewArtifact `json:"artifacts"` + Token string `json:"token"` + FullToken string `json:"full_token"` + Tap string `json:"tap"` + Name []string `json:"name"` + Desc string `json:"desc"` + URL string `json:"url"` + Version string `json:"version"` + SHA256 string `json:"sha256"` + Artifacts []*brewArtifact `json:"artifacts"` + PreUninstallScripts []string `json:"-"` + PostUninstallScripts []string `json:"-"` } // brew artifacts are objects that have one and only one of their fields set. diff --git a/server/mdm/maintainedapps/scripts.go b/server/mdm/maintainedapps/scripts.go index 6b748af75e..e5b5da7005 100644 --- a/server/mdm/maintainedapps/scripts.go +++ b/server/mdm/maintainedapps/scripts.go @@ -77,9 +77,15 @@ func uninstallScriptForApp(cask *brewCask) string { } case len(artifact.Uninstall) > 0: sortUninstall(artifact.Uninstall) + if len(cask.PreUninstallScripts) > 0 { + sb.Write(strings.Join(cask.PreUninstallScripts, "\n")) + } for _, u := range artifact.Uninstall { processUninstallArtifact(u, sb) } + if len(cask.PostUninstallScripts) > 0 { + sb.Write(strings.Join(cask.PostUninstallScripts, "\n")) + } case len(artifact.Zap) > 0: sortUninstall(artifact.Zap) for _, z := range artifact.Zap { @@ -179,11 +185,11 @@ func processUninstallArtifact(u *brewUninstall, sb *scriptBuilder) { if u.Script.IsOther { addUserVar() for _, path := range u.Script.Other { - sb.Writef(`sudo -u "$LOGGED_IN_USER" '%s'`, path) + sb.Writef(`(cd /Users/$LOGGED_IN_USER && sudo -u "$LOGGED_IN_USER" '%s')`, path) } } else if len(u.Script.String) > 0 { addUserVar() - sb.Writef(`sudo -u "$LOGGED_IN_USER" '%s'`, u.Script.String) + sb.Writef(`(cd /Users/$LOGGED_IN_USER && sudo -u "$LOGGED_IN_USER" '%s')`, u.Script.String) } process(u.PkgUtil, func(pkgID string) { @@ -365,15 +371,15 @@ const removeLaunchctlServiceFunc = `remove_launchctl_service() { local booleans=("true" "false") local plist_status local paths - local sudo + local should_sudo echo "Removing launchctl service ${service}" - for sudo in "${booleans[@]}"; do + for should_sudo in "${booleans[@]}"; do plist_status=$(launchctl list "${service}" 2>/dev/null) if [[ $plist_status == \{* ]]; then - if [[ $sudo == "true" ]]; then + if [[ $should_sudo == "true" ]]; then sudo launchctl remove "${service}" else launchctl remove "${service}" @@ -387,7 +393,7 @@ const removeLaunchctlServiceFunc = `remove_launchctl_service() { ) # if not using sudo, prepend the home directory to the paths - if [[ $sudo == "false" ]]; then + if [[ $should_sudo == "false" ]]; then for i in "${!paths[@]}"; do paths[i]="${HOME}${paths[i]}" done @@ -395,7 +401,7 @@ const removeLaunchctlServiceFunc = `remove_launchctl_service() { for path in "${paths[@]}"; do if [[ -e "$path" ]]; then - if [[ $sudo == "true" ]]; then + if [[ $should_sudo == "true" ]]; then sudo rm -f -- "$path" else rm -f -- "$path" @@ -450,6 +456,7 @@ const trashFunc = `trash() { local logged_in_user="$1" local target_file="$2" local timestamp="$(date +%Y-%m-%d-%s)" + local rand="$(jot -r 1 0 99999)" # replace ~ with /Users/$logged_in_user if [[ "$target_file" == ~* ]]; then @@ -461,7 +468,7 @@ const trashFunc = `trash() { if [[ -e "$target_file" ]]; then echo "removing $target_file." - mv -f "$target_file" "$trash/${file_name}_${timestamp}" + mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" else echo "$target_file doesn't exist." fi