From 0d23100a4983c629aeaeaddb71decc781523f7df Mon Sep 17 00:00:00 2001 From: wh1te909 Date: Mon, 19 Oct 2020 03:28:33 -0700 Subject: [PATCH] refactor win services to fix bug where handle was never closed --- agent/agent_windows.go | 160 ++---------------- agent/checks_windows.go | 8 +- agent/install_windows.go | 28 +-- agent/services_windows.go | 154 +++++++++++++++++ ...vice_windows.go => winagentsvc_windows.go} | 0 main.go | 8 +- 6 files changed, 174 insertions(+), 184 deletions(-) create mode 100644 agent/services_windows.go rename agent/{service_windows.go => winagentsvc_windows.go} (100%) diff --git a/agent/agent_windows.go b/agent/agent_windows.go index a240363..5b46ce6 100644 --- a/agent/agent_windows.go +++ b/agent/agent_windows.go @@ -21,7 +21,6 @@ import ( "github.com/gonutz/w32" _ "github.com/mattn/go-sqlite3" // ok "github.com/shirou/gopsutil/disk" - svc "github.com/shirou/gopsutil/winservices" "github.com/sirupsen/logrus" "golang.org/x/sys/windows" "golang.org/x/sys/windows/registry" @@ -190,132 +189,6 @@ func OSInfo() (plat, osFullName string) { return } -// https://docs.microsoft.com/en-us/dotnet/api/system.serviceprocess.servicecontrollerstatus?view=dotnet-plat-ext-3.1 -func serviceStatusText(num uint32) string { - switch num { - case 1: - return "stopped" - case 2: - return "start_pending" - case 3: - return "stop_pending" - case 4: - return "running" - case 5: - return "continue_pending" - case 6: - return "pause_pending" - case 7: - return "paused" - default: - return "unknown" - } -} - -// https://docs.microsoft.com/en-us/dotnet/api/system.serviceprocess.servicestartmode?view=dotnet-plat-ext-3.1 -func serviceStartType(num uint32) string { - switch num { - case 0: - return "Boot" - case 1: - return "System" - case 2: - return "Automatic" - case 3: - return "Manual" - case 4: - return "Disabled" - default: - return "Unknown" - } -} - -// WindowsService holds windows service info -type WindowsService struct { - Name string `json:"name"` - Status string `json:"status"` - DisplayName string `json:"display_name"` - BinPath string `json:"binpath"` - Description string `json:"description"` - Username string `json:"username"` - PID uint32 `json:"pid"` - StartType string `json:"start_type"` -} - -// WinServiceGet mimics psutils win_service_get -func WinServiceGet(name string) (*svc.Service, error) { - srv, err := svc.NewService(name) - if err != nil { - return &svc.Service{}, err - } - return srv, nil -} - -// WaitForService will wait for a service to be in X state for X retries -func WaitForService(name string, status string, retries int) { - attempts := 0 - for { - service, err := WinServiceGet(name) - if err != nil { - attempts++ - time.Sleep(5 * time.Second) - } else { - service.GetServiceDetail() - stat := serviceStatusText(uint32(service.Status.State)) - if stat != status { - attempts++ - time.Sleep(5 * time.Second) - } else { - attempts = 0 - } - } - if attempts == 0 || attempts >= retries { - break - } - } -} - -// GetServices returns a list of windows services -func (a *WindowsAgent) GetServices() []WindowsService { - ret := make([]WindowsService, 0) - services, err := svc.ListServices() - if err != nil { - a.Logger.Debugln(err) - return ret - } - - for _, s := range services { - srv, err := svc.NewService(s.Name) - if err != nil { - continue - } - - derr := srv.GetServiceDetail() - conf, qerr := srv.QueryServiceConfig() - if derr == nil && qerr == nil { - winsvc := WindowsService{ - Name: s.Name, - Status: serviceStatusText(uint32(srv.Status.State)), - DisplayName: conf.DisplayName, - BinPath: conf.BinaryPathName, - Description: conf.Description, - Username: conf.ServiceStartName, - PID: uint32(srv.Status.Pid), - StartType: serviceStartType(uint32(conf.StartType)), - } - ret = append(ret, winsvc) - } else { - if derr != nil { - a.Logger.Debugln(derr) - } - if qerr != nil { - a.Logger.Debugln(qerr) - } - } - } - return ret -} - // Disk holds physical disk info type Disk struct { Device string `json:"device"` @@ -585,12 +458,11 @@ func (a *WindowsAgent) ForceKillMesh() { func (a *WindowsAgent) RecoverSalt() { saltSVC := "salt-minion" a.Logger.Debugln("Attempting salt recovery on", a.Hostname) - defer CMD(a.Nssm, []string{"start", saltSVC}, 45, false) + defer CMD(a.Nssm, []string{"start", saltSVC}, 60, false) - CMD(a.Nssm, []string{"stop", saltSVC}, 45, false) - WaitForService(saltSVC, "stopped", 15) + _, _ = CMD(a.Nssm, []string{"stop", saltSVC}, 120, false) a.ForceKillSalt() - CMD("ipconfig", []string{"flushdns"}, 15, false) + _, _ = CMD("ipconfig", []string{"flushdns"}, 15, false) a.Logger.Debugln("Salt recovery completed on", a.Hostname) } @@ -663,11 +535,9 @@ func (a *WindowsAgent) SyncMeshNodeID() { //RecoverMesh recovers mesh agent func (a *WindowsAgent) RecoverMesh() { a.Logger.Debugln("Attempting mesh recovery on", a.Hostname) - defer CMD("sc.exe", []string{"start", a.MeshSVC}, 20, false) + defer CMD("net", []string{"start", a.MeshSVC}, 60, false) - args := []string{"stop", a.MeshSVC} - CMD("sc.exe", args, 45, false) - WaitForService(a.MeshSVC, "stopped", 5) + _, _ = CMD("net", []string{"stop", a.MeshSVC}, 60, false) a.ForceKillMesh() a.SyncMeshNodeID() } @@ -761,16 +631,12 @@ func ShowStatus(version string) { svcs := []string{"tacticalagent", "checkrunner", "salt-minion", "mesh agent"} for _, service := range svcs { - srv, err := WinServiceGet(service) + status, err := GetServiceStatus(service) if err != nil { statusMap[service] = "Not Installed" continue } - if derr := srv.GetServiceDetail(); derr != nil { - statusMap[service] = "Unknown" - continue - } - statusMap[service] = serviceStatusText(uint32(srv.Status.State)) + statusMap[service] = status } window := w32.GetForegroundWindow() @@ -931,9 +797,10 @@ func (a *WindowsAgent) UpdateSalt() { a.Logger.Errorln("Unable to download salt-minion for update:", r1.StatusCode()) return } + _, _ = CMD(a.Nssm, []string{"stop", "salt-minion"}, 120, false) + _ = os.Chdir(a.ProgramDir) updateArgs := []string{ - a.SaltInstaller, "/S", "/custom-config=saltcustom", fmt.Sprintf("/master=%s", a.DB.SaltMaster), @@ -941,21 +808,14 @@ func (a *WindowsAgent) UpdateSalt() { "/start-minion=1", } - _ = os.Chdir(a.ProgramDir) + _, saltErr := CMD(a.SaltInstaller, updateArgs, 900, false) - _, _ = CMD(a.Nssm, []string{"stop", "salt-minion"}, 90, false) - WaitForService("salt-minion", "stopped", 10) - a.ForceKillSalt() - - _, saltErr := CMDShell(updateArgs, "", 900, false) if saltErr != nil { a.Logger.Errorln("Failed to update salt:", saltErr) _, _ = CMD(a.Nssm, []string{"start", "salt-minion"}, 60, false) return } - WaitForService("salt-minion", "running", 10) - req.URL = a.Server + "/api/v3/saltminion/" req.Method = "PUT" req.Payload = map[string]string{"ver": data.LatestVer, "agent_id": a.AgentID} diff --git a/agent/checks_windows.go b/agent/checks_windows.go index 79a1ba6..075f256 100644 --- a/agent/checks_windows.go +++ b/agent/checks_windows.go @@ -535,17 +535,11 @@ func (a *WindowsAgent) WinSvcCheck(data Check) { Debug: a.Debug, } - srv, err := WinServiceGet(data.ServiceName) + status, err := GetServiceStatus(data.ServiceName) if err != nil { exists = false status = "n/a" a.Logger.Debugln("Service", data.ServiceName, err) - } else { - if derr := srv.GetServiceDetail(); derr != nil { - a.Logger.Debugln("Service", data.ServiceName, err) - return - } - status = serviceStatusText(uint32(srv.Status.State)) } r.Payload = map[string]interface{}{ diff --git a/agent/install_windows.go b/agent/install_windows.go index e8cf76b..0022e4e 100644 --- a/agent/install_windows.go +++ b/agent/install_windows.go @@ -159,10 +159,6 @@ func (a *WindowsAgent) Install(i *Installer) { } fmt.Println(meshOut) - - a.Logger.Debugln("Waiting for mesh service to be running") - WaitForService(a.MeshSVC, "running", 15) - a.Logger.Debugln("Mesh service is running") a.Logger.Debugln("Sleeping for 10") time.Sleep(10 * time.Second) @@ -273,7 +269,6 @@ func (a *WindowsAgent) Install(i *Installer) { a.Logger.Infoln("Installing the salt-minion, this might take a while...") saltInstallArgs := []string{ - a.SaltInstaller, "/S", "/custom-config=saltcustom", fmt.Sprintf("/master=%s", i.SaltMaster), @@ -282,20 +277,12 @@ func (a *WindowsAgent) Install(i *Installer) { } a.Logger.Debugln("Installing salt with:", saltInstallArgs) - _, saltErr := CMDShell(saltInstallArgs, "", int(i.Timeout), false) + _, saltErr := CMD(a.SaltInstaller, saltInstallArgs, int(i.Timeout), false) if saltErr != nil { a.installerMsg(fmt.Sprintf("Unable to install salt: %s", saltErr.Error()), "error") } - a.Logger.Debugln("Waiting for salt-minion service enter the running state") - WaitForService("salt-minion", "running", 30) - a.Logger.Debugln("Salt-minion is running") - _, serr := WinServiceGet("salt-minion") - if serr != nil { - a.installerMsg("Salt installation failed\nCheck the log file in c:\\salt\\var\\log\\salt\\minion", "error") - } - - time.Sleep(5 * time.Second) + time.Sleep(10 * time.Second) // set new headers, no longer knox auth...use agent auth rClient.SetHeaders(a.Headers) @@ -368,17 +355,6 @@ func (a *WindowsAgent) Install(i *Installer) { a.Logger.Debugln("Creating mesh watchdog scheduled task") a.CreateMeshWatchDogTask() - // remove existing services if exist - services := []string{"tacticalagent", "checkrunner"} - for _, svc := range services { - _, err := WinServiceGet(svc) - if err == nil { - a.Logger.Debugln(fmt.Sprintf("Found existing %s service. Removing", svc)) - _, _ = CMD(a.Nssm, []string{"stop", svc}, 30, false) - _, _ = CMD(a.Nssm, []string{"remove", svc, "confirm"}, 30, false) - } - } - a.Logger.Infoln("Installing services...") svcCommands := [8][]string{ // winagentsvc diff --git a/agent/services_windows.go b/agent/services_windows.go new file mode 100644 index 0000000..d3e75a9 --- /dev/null +++ b/agent/services_windows.go @@ -0,0 +1,154 @@ +package agent + +import ( + "golang.org/x/sys/windows/svc/mgr" + "time" +) + +// WindowsService holds windows service info +type WindowsService struct { + Name string `json:"name"` + Status string `json:"status"` + DisplayName string `json:"display_name"` + BinPath string `json:"binpath"` + Description string `json:"description"` + Username string `json:"username"` + PID uint32 `json:"pid"` + StartType string `json:"start_type"` +} + +func GetServiceStatus(name string) (string, error) { + conn, err := mgr.Connect() + if err != nil { + return "n/a", err + } + defer conn.Disconnect() + + srv, err := conn.OpenService(name) + if err != nil { + return "n/a", err + } + defer srv.Close() + + q, err := srv.Query() + if err != nil { + return "n/a", err + } + + return serviceStatusText(uint32(q.State)), nil +} + +// GetServices returns a list of windows services +func (a *WindowsAgent) GetServices() []WindowsService { + ret := make([]WindowsService, 0) + + conn, err := mgr.Connect() + if err != nil { + a.Logger.Debugln(err) + return ret + } + defer conn.Disconnect() + + svcs, err := conn.ListServices() + + if err != nil { + a.Logger.Debugln(err) + return ret + } + + for _, s := range svcs { + srv, err := conn.OpenService(s) + if err != nil { + a.Logger.Debugln(err) + continue + } + defer srv.Close() + + q, err := srv.Query() + if err != nil { + a.Logger.Debugln(err) + continue + } + + conf, err := srv.Config() + if err != nil { + a.Logger.Debugln(err) + continue + } + + winsvc := WindowsService{ + Name: s, + Status: serviceStatusText(uint32(q.State)), + DisplayName: conf.DisplayName, + BinPath: conf.BinaryPathName, + Description: conf.Description, + Username: conf.ServiceStartName, + PID: q.ProcessId, + StartType: serviceStartType(uint32(conf.StartType)), + } + ret = append(ret, winsvc) + } + return ret +} + +// WaitForService will wait for a service to be in X state for X retries +func WaitForService(name string, status string, retries int) { + attempts := 0 + for { + stat, err := GetServiceStatus(name) + if err != nil { + attempts++ + time.Sleep(5 * time.Second) + } else { + if stat != status { + attempts++ + time.Sleep(5 * time.Second) + } else { + attempts = 0 + } + } + if attempts == 0 || attempts >= retries { + break + } + } +} + +// https://docs.microsoft.com/en-us/dotnet/api/system.serviceprocess.servicecontrollerstatus?view=dotnet-plat-ext-3.1 +func serviceStatusText(num uint32) string { + switch num { + case 1: + return "stopped" + case 2: + return "start_pending" + case 3: + return "stop_pending" + case 4: + return "running" + case 5: + return "continue_pending" + case 6: + return "pause_pending" + case 7: + return "paused" + default: + return "unknown" + } +} + +// https://docs.microsoft.com/en-us/dotnet/api/system.serviceprocess.servicestartmode?view=dotnet-plat-ext-3.1 +func serviceStartType(num uint32) string { + switch num { + case 0: + return "Boot" + case 1: + return "System" + case 2: + return "Automatic" + case 3: + return "Manual" + case 4: + return "Disabled" + default: + return "Unknown" + } +} diff --git a/agent/service_windows.go b/agent/winagentsvc_windows.go similarity index 100% rename from agent/service_windows.go rename to agent/winagentsvc_windows.go diff --git a/main.go b/main.go index 1824082..3187038 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "flag" "fmt" "os" + "path/filepath" "runtime" "github.com/sirupsen/logrus" @@ -119,7 +120,12 @@ func setupLogging(level *string, to *string) { if *to == "stdout" { log.SetOutput(os.Stdout) } else { - logFile, _ = os.OpenFile("agent.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0664) + switch runtime.GOOS { + case "windows": + logFile, _ = os.OpenFile(filepath.Join(os.Getenv("ProgramFiles"), "TacticalAgent", "agent.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0664) + case "linux": + // todo + } log.SetOutput(logFile) } }