refactor win services to fix bug where handle was never closed

This commit is contained in:
wh1te909 2020-10-19 03:28:33 -07:00
parent bfc440862d
commit 0d23100a49
6 changed files with 174 additions and 184 deletions

View File

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

View File

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

View File

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

154
agent/services_windows.go Normal file
View File

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

View File

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