mirror of https://github.com/wh1te909/rmmagent.git
refactor win services to fix bug where handle was never closed
This commit is contained in:
parent
bfc440862d
commit
0d23100a49
|
@ -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}
|
||||
|
|
|
@ -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{}{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
8
main.go
8
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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue