Skip to content

Commit

Permalink
Merge pull request #36 from redhatrises/uninstall
Browse files Browse the repository at this point in the history
feat: add uninstall capability
  • Loading branch information
redhatrises authored Nov 23, 2024
2 parents 1fe6979 + ae95566 commit da39a12
Show file tree
Hide file tree
Showing 4 changed files with 233 additions and 3 deletions.
24 changes: 21 additions & 3 deletions internal/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,15 @@ func rootCmd() *cobra.Command {
}
groups["Falcon API Flags"] = apiFlag

uninstallFlag := pflag.NewFlagSet("Uninstall", pflag.ExitOnError)
uninstallFlag.Bool("uninstall", false, "Uninstall the Falcon sensor")
rootCmd.Flags().AddFlagSet(uninstallFlag)
err = viper.BindPFlags(uninstallFlag)
if err != nil {
log.Fatalf("Error binding falcon uninstall flags: %v", err)
}
groups["Falcon Uninstall Flags"] = uninstallFlag

// Falcon sensor flags
falconFlag := pflag.NewFlagSet("Falcon", pflag.ExitOnError)
falconFlag.StringVar(&fc.CID, "cid", "", "Falcon Customer ID. Optional when OAuth2 credentials are provided")
Expand Down Expand Up @@ -240,6 +249,11 @@ func preRunValidation(cmd *cobra.Command, args []string) error {
// on why the command failed.
cmd.SilenceUsage = true

// Skip the validation if uninstall flag is set
if cmd.Flags().Changed("uninstall") || viper.GetBool("uninstall") {
return nil
}

// ClientID and ClientSecret cannot be set when Access Token is provided
if cmd.Flags().Changed("access-token") && (cmd.Flags().Changed("client-id") || cmd.Flags().Changed("client-secret")) {
return fmt.Errorf("Cannot specify Client ID or Client Secret when Access Token is provided")
Expand Down Expand Up @@ -324,10 +338,14 @@ func Run(cmd *cobra.Command, args []string) {
fi.OsVersion = osVersion
fi.SensorConfig = fc

slog.Debug("Falcon sensor CLI options", "CID", fc.CID, "ProvisioningToken", fc.ProvisioningToken, "Tags", fc.Tags, "DisableProxy", fc.ProxyDisable, "ProxyHost", fc.ProxyHost, "ProxyPort", fc.ProxyPort)
slog.Debug("Falcon installer options", "Cloud", fi.Cloud, "MemberCID", fi.MemberCID, "SensorUpdatePolicyName", fi.SensorUpdatePolicyName, "GpgKeyFile", fi.GpgKeyFile, "TmpDir", fi.TmpDir, "OsName", fi.OsName, "OsVersion", fi.OsVersion, "OS", fi.OSType, "Arch", fi.Arch)
if !cmd.Flags().Changed("uninstall") {
slog.Debug("Falcon sensor CLI options", "CID", fc.CID, "ProvisioningToken", fc.ProvisioningToken, "Tags", fc.Tags, "DisableProxy", fc.ProxyDisable, "ProxyHost", fc.ProxyHost, "ProxyPort", fc.ProxyPort)
slog.Debug("Falcon installer options", "Cloud", fi.Cloud, "MemberCID", fi.MemberCID, "SensorUpdatePolicyName", fi.SensorUpdatePolicyName, "GpgKeyFile", fi.GpgKeyFile, "TmpDir", fi.TmpDir, "OsName", fi.OsName, "OsVersion", fi.OsVersion, "OS", fi.OSType, "Arch", fi.Arch)

installer.Run(fi)
installer.Run(fi)
} else {
installer.Uninstall(fi)
}
}

// inputValidation validates the input against the provided regex pattern.
Expand Down
118 changes: 118 additions & 0 deletions pkg/installer/uninstall.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// MIT License
//
// Copyright (c) 2024 CrowdStrike
//
// 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 installer

import (
"log"
"log/slog"
"os/exec"

"github.com/crowdstrike/falcon-installer/pkg/falcon/falconctl"
"github.com/crowdstrike/falcon-installer/pkg/utils"
"github.com/crowdstrike/falcon-installer/pkg/utils/osutils"
)

// Uninstall removes the Falcon Sensor from the target system.
func Uninstall(fc FalconInstaller) {
falconInstalled, err := osutils.FalconInstalled(fc.OSType)
if err != nil {
log.Fatalf("Error checking if Falcon sensor is installed: %v", err)
}

// Uninstall the Falcon Sensor
if falconInstalled {
fc.uninstallSensor()
}
slog.Info("Falcon Sensor has been uninstalled")
}

// installSensor installs the Falcon sensor using the appropriate package manager.
func (fc FalconInstaller) uninstallSensor() {
c := ""
env := ""
args := []string{} //nolint:staticcheck

switch fc.OSType {
case "linux":
sensor := "falcon-sensor"

if cmd, err := exec.LookPath("/usr/bin/dnf"); err == nil {
c = cmd
args = []string{"remove", "-q", "-y", sensor}
} else if cmd, err := exec.LookPath("/usr/bin/yum"); err == nil {
c = cmd
args = []string{"remove", "-q", "-y", sensor}
} else if cmd, err := exec.LookPath("/usr/bin/zypper"); err == nil {
c = cmd
args = []string{"remove", "--quiet", "-y", sensor}
} else if cmd, err := exec.LookPath("/usr/bin/apt-get"); err == nil {
c = cmd
args = []string{"purge", "-y", sensor}
env = "DEBIAN_FRONTEND=noninteractive"
} else if cmd, err := exec.LookPath("/usr/bin/dpkg"); err == nil {
c = cmd
args = []string{"remove", "--qq", "-y", sensor}
env = "DEBIAN_FRONTEND=noninteractive"
} else {
log.Fatalf("Unable to find expected linux package manager. Unsupported package manager: %v", err)
}

slog.Debug("Uninstall command being used for removal", "Command", c, "Args", args)
stdout, stderr, err := installSensorWithRetry(c, env, args)
if err != nil {
log.Fatalf("Error running %s: %v, stdout: %s, stderr: %s", c, err, string(stdout), string(stderr))
}

slog.Debug("Removing Falcon Sensor", string(stdout), string(stderr))
case "windows":
uninstallArgs := []string{"/uninstall", "/quiet"}
uninstallRegex := `^((WindowsSensor|FalconSensor_Windows).*\.)(exe)$`
dir := "C:\\ProgramData\\Package Cache"

slog.Debug("Finding the Falcon Sensor uninstaller", "Directory", dir, "Regex", uninstallRegex)
uninstaller, err := utils.FindFile(dir, uninstallRegex)
if err != nil {
log.Fatalf("Error finding the Windows Sensor uninstaller: %v", err)
}

slog.Debug("Running the Falcon Sensor uninstaller", "Uninstaller", uninstaller, "Args", uninstallArgs)
stdout, stderr, err := utils.RunCmd(uninstaller, uninstallArgs)
if err != nil {
log.Fatalf("Error running %s: %v, stdout: %s, stderr: %s", uninstaller, err, string(stdout), string(stderr))
}

slog.Debug("Removing Falcon Sensor")
case "macos":
slog.Debug("Unloading the Falcon Sensor")
if err := falconctl.Set(fc.OSType, fc.macosArgHandler("unload")); err != nil {
log.Fatalf("Error configuring Falcon sensor: %v", err)
}

slog.Debug("Uninstalling the Falcon Sensor")
if err := falconctl.Set(fc.OSType, fc.macosArgHandler("uninstall")); err != nil {
log.Fatalf("Error configuring Falcon sensor: %v", err)
}
default:
log.Fatalf("Unable to begin package installation. Unsupported OS: %s", fc.OSType)
}
}
39 changes: 39 additions & 0 deletions pkg/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,54 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
)

// BoolToInt converts a boolean to an integer.
func BoolToInt(b bool) uint8 {
if b {
return 1
}
return 0
}

// FindFile searches for a file in a directory that matches a regular expression.
func FindFile(dir string, regex string) (string, error) {
found := ""

err := filepath.Walk(dir, func(path string, file os.FileInfo, err error) error {
if err != nil {
if !os.IsPermission(err) {
return err
}
}

if !file.IsDir() {
f, err := regexp.MatchString(regex, file.Name())
if err != nil {
return err
}
if f {
found = path
return filepath.SkipDir // Stop searching once the file is found
}
}

return nil
})
if err != nil {
return "", err
}

if found == "" {
return "", fmt.Errorf("Unable to find files in '%s' with regex: %s", dir, regex)
}

return found, nil
}

// RunCmdWithEnv runs a command with the specified environment variables.
func RunCmdWithEnv(cmnd string, env string, arg []string) ([]byte, []byte, error) {
var stdout, stderr bytes.Buffer
c := exec.Command(cmnd, arg...)
Expand All @@ -58,6 +96,7 @@ func RunCmdWithEnv(cmnd string, env string, arg []string) ([]byte, []byte, error
return stdout.Bytes(), stderr.Bytes(), err
}

// RunCmd runs a command.
func RunCmd(cmnd string, arg []string) ([]byte, []byte, error) {
var stdout, stderr bytes.Buffer
c := exec.Command(cmnd, arg...)
Expand Down
55 changes: 55 additions & 0 deletions pkg/utils/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,61 @@ func TestBoolToInt(t *testing.T) {
}
}

func TestFindFile(t *testing.T) {
switch runtime.GOOS {
case "windows":
file, err := FindFile("C:\\testingFakeDIr", "")
if err == nil {
t.Errorf("Expected error: %v, got: %v", err, file)
}

file, err = FindFile("C:\\Windows", "\\")
if err == nil {
t.Errorf("Expected error: %v, got: %v", err, file)
}

file, err = FindFile("C:\\Windows\\System32\\Sysprep", "not[goingtofindthis]file")
if err == nil {
t.Errorf("Expected error: %v, got: %v", err, file)
}

file, err = FindFile("C:\\Windows\\System32\\Sysprep", "sys.*exe")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}

if !strings.Contains(file, "sysprep.exe") {
t.Errorf("Expected input: %q, got: %q", "sysprep.exe", file)
}

default:
file, err := FindFile("/testingFakeDIr", "")
if err == nil {
t.Errorf("Expected error: %v, got: %v", err, file)
}

file, err = FindFile("/etc", "\\")
if err == nil {
t.Errorf("Expected error: %v, got: %v", err, file)
}

file, err = FindFile("/etc/", "not[goingtofindthis]file")
if err == nil {
t.Errorf("Expected error: %v, got: %v", err, file)
}

file, err = FindFile("/etc/", "h[o]+sts")
if err != nil {
t.Errorf("Expected error: %v, got: %v", err, file)
}

if !strings.Contains(file, "hosts") {
t.Errorf("Expected input: %q, got: %q", "hosts", file)
}

}
}

func TestRunCmdWithEnv(t *testing.T) {
cmd, args, newline := testCmnd()
env := "FOO=bar"
Expand Down

0 comments on commit da39a12

Please sign in to comment.