From 6ba3272dc0126bf4a5c4b0e8b17597de4446858c Mon Sep 17 00:00:00 2001 From: David Randall Date: Sun, 12 Nov 2023 13:24:24 -0500 Subject: [PATCH 1/3] [Feature] Add cross site scripting --- agent/agent_unix.go | 43 ++++++++++++++++++++++++++++++++++++++++-- agent/agent_windows.go | 8 ++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/agent/agent_unix.go b/agent/agent_unix.go index 80d81c1..c77c52e 100644 --- a/agent/agent_unix.go +++ b/agent/agent_unix.go @@ -194,10 +194,49 @@ func (a *Agent) RunScript(code string, shell string, args []string, timeout int, opts := a.NewCMDOpts() opts.IsScript = true - opts.Shell = f.Name() - opts.Args = args + switch shell { + case "nushell": + // FIXME: Make this dynamic and use /opt/tacticalagent/bin/nu + opts.Shell = "/usr/local/bin/nu" + opts.Args = append([]string{ + "--no-config-file", + f.Name(), + }, + args...) + + case "deno": + // FIXME: Make this dynamic and use /opt/tacticalagent/bin/nu + opts.Shell = "/usr/local/bin/deno" + opts.Args = []string{ + "run", + "--no-prompt", + } + + // Search the environment variables for DENO_PERMISSIONS and use that to set permissions for the script. + // https://docs.deno.com/runtime/manual/basics/permissions#permissions-list + // DENO_PERMISSIONS is not an official environment variable. + // https://docs.deno.com/runtime/manual/basics/env_variables + // TODO: Remove DENO_PERMISSIONS from the environment variables. + for _, v := range envVars { + if strings.HasPrefix(v, "DENO_PERMISSIONS=") { + permissions := strings.Split(v, "=")[1] + opts.Args = append(opts.Args, strings.Split(permissions, " ")...) + } + } + + // Can't append a variadic slice after a string arg. + // https://pkg.go.dev/builtin#append + opts.Args = append(opts.Args, f.Name()) + opts.Args = append(opts.Args, args...) + + default: + opts.Shell = f.Name() + opts.Args = args + } + opts.EnvVars = envVars opts.Timeout = time.Duration(timeout) + a.Logger.Debugln("RunScript(): ", opts.Shell, opts.Args) out := a.CmdV2(opts) retError := "" if out.Status.Error != nil { diff --git a/agent/agent_windows.go b/agent/agent_windows.go index 140c403..d9f6678 100644 --- a/agent/agent_windows.go +++ b/agent/agent_windows.go @@ -118,6 +118,10 @@ func (a *Agent) RunScript(code string, shell string, args []string, timeout int, ext = "*.py" case "cmd": ext = "*.bat" + case "nushell": + ext = "*.nu" + case "deno": + ext = "*.ts" } tmpDir := a.WinTmpDir @@ -151,6 +155,10 @@ func (a *Agent) RunScript(code string, shell string, args []string, timeout int, cmdArgs = []string{tmpfn.Name()} case "cmd": exe = tmpfn.Name() + case "nushell": + exe = tmpfn.Name() + case "deno": + exe = tmpfn.Name() } if len(args) > 0 { From 87e1b29ef6e203d5e72f72f4203b5941e2eb79b7 Mon Sep 17 00:00:00 2001 From: David Randall Date: Sat, 18 Nov 2023 20:03:29 -0500 Subject: [PATCH 2/3] Add: Download the nu and deno binaries from GitHub Signed-off-by: David Randall --- agent/agent.go | 23 ++++ agent/agent_unix.go | 255 +++++++++++++++++++++++++++++++++++++++-- agent/agent_windows.go | 235 ++++++++++++++++++++++++++++++++++++- agent/install.go | 12 +- agent/rpc.go | 4 + agent/svc.go | 3 + agent/utils.go | 67 +++++++++++ main.go | 4 + 8 files changed, 592 insertions(+), 11 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 1cc74cf..cd3c895 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -58,6 +58,8 @@ type Agent struct { MeshSystemEXE string MeshSVC string PyBin string + NuBin string + DenoBin string Headers map[string]string Logger *logrus.Logger Version string @@ -86,10 +88,13 @@ const ( nixAgentDir = "/opt/tacticalagent" nixMeshDir = "/opt/tacticalmesh" nixAgentBin = nixAgentDir + "/tacticalagent" + nixAgentBinDir = nixAgentDir + "/bin" nixMeshAgentBin = nixMeshDir + "/meshagent" macPlistPath = "/Library/LaunchDaemons/tacticalagent.plist" macPlistName = "tacticalagent" defaultMacMeshSvcDir = "/usr/local/mesh_services" + nuVersion = "0.87.0" + denoVersion = "v1.38.2" ) var defaultWinTmpDir = filepath.Join(os.Getenv("PROGRAMDATA"), "TacticalRMM") @@ -119,6 +124,22 @@ func New(logger *logrus.Logger, version string) *Agent { pybin = filepath.Join(pd, "py38-x32", "python.exe") } + var nuBin string + switch runtime.GOOS { + case "windows": + nuBin = filepath.Join(pd, "bin", "nu.exe") + default: + nuBin = filepath.Join(nixAgentBinDir, "nu") + } + + var denoBin string + switch runtime.GOOS { + case "windows": + denoBin = filepath.Join(pd, "bin", "deno.exe") + default: + denoBin = filepath.Join(nixAgentBinDir, "deno") + } + ac := NewAgentConfig() headers := make(map[string]string) @@ -232,6 +253,8 @@ func New(logger *logrus.Logger, version string) *Agent { MeshSystemEXE: MeshSysExe, MeshSVC: meshSvcName, PyBin: pybin, + NuBin: nuBin, + DenoBin: denoBin, Headers: headers, Logger: logger, Version: version, diff --git a/agent/agent_unix.go b/agent/agent_unix.go index c77c52e..aebd808 100644 --- a/agent/agent_unix.go +++ b/agent/agent_unix.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" "os" + "path" "path/filepath" "runtime" "strconv" @@ -196,31 +197,42 @@ func (a *Agent) RunScript(code string, shell string, args []string, timeout int, opts.IsScript = true switch shell { case "nushell": - // FIXME: Make this dynamic and use /opt/tacticalagent/bin/nu - opts.Shell = "/usr/local/bin/nu" + opts.Shell = a.NuBin opts.Args = append([]string{ "--no-config-file", f.Name(), }, args...) + if !trmm.FileExists(a.NuBin) { + a.Logger.Errorln("RunScript(): Executable does not exist. Install Nu and try again:", a.NuBin) + err := errors.New("File Not Found: " + a.NuBin) + return "", err.Error(), 85, err + } case "deno": - // FIXME: Make this dynamic and use /opt/tacticalagent/bin/nu - opts.Shell = "/usr/local/bin/deno" + opts.Shell = a.DenoBin opts.Args = []string{ "run", "--no-prompt", } + if !trmm.FileExists(a.DenoBin) { + a.Logger.Errorln("RunScript(): Executable does not exist. Install deno and try again:", a.DenoBin) + err := errors.New("File Not Found: " + a.DenoBin) + return "", err.Error(), 85, err + } // Search the environment variables for DENO_PERMISSIONS and use that to set permissions for the script. // https://docs.deno.com/runtime/manual/basics/permissions#permissions-list // DENO_PERMISSIONS is not an official environment variable. // https://docs.deno.com/runtime/manual/basics/env_variables - // TODO: Remove DENO_PERMISSIONS from the environment variables. - for _, v := range envVars { + for i, v := range envVars { if strings.HasPrefix(v, "DENO_PERMISSIONS=") { permissions := strings.Split(v, "=")[1] opts.Args = append(opts.Args, strings.Split(permissions, " ")...) + // Remove the DENO_PERMISSIONS variable from the environment variables slice. + // It's possible more variables may exist with the same prefix. + envVars = append(envVars[:i], envVars[i+1:]...) + break } } @@ -236,7 +248,7 @@ func (a *Agent) RunScript(code string, shell string, args []string, timeout int, opts.EnvVars = envVars opts.Timeout = time.Duration(timeout) - a.Logger.Debugln("RunScript(): ", opts.Shell, opts.Args) + a.Logger.Debugln("RunScript():", opts.Shell, opts.Args) out := a.CmdV2(opts) retError := "" if out.Status.Error != nil { @@ -541,6 +553,235 @@ func GetServiceStatus(name string) (string, error) { return "", nil } func (a *Agent) GetPython(force bool) {} +// GetNushell will download nushell from GitHub and install (copy) it to nixAgentBinDir +func (a *Agent) GetNushell(force bool) { + if trmm.FileExists(a.NuBin) { + if force { + err := os.Remove(a.NuBin) + if err != nil { + a.Logger.Errorln("GetNushell(): Error removing nu binary:", err) + return + } + } else { + return + } + } + + if !trmm.FileExists(nixAgentBinDir) { + err := os.MkdirAll(nixAgentBinDir, 0755) + if err != nil { + a.Logger.Errorln("GetNushell(): Error creating nixAgentBinDir:", err) + return + } + } + + var ( + assetName string + url string + targzDirName string + ) + + switch runtime.GOOS { + case "darwin": + switch runtime.GOARCH { + case "arm64": + // https://github.com/nushell/nushell/releases/download/0.87.0/nu-0.87.0-aarch64-darwin-full.tar.gz + assetName = fmt.Sprintf("nu-%s-aarch64-darwin-full.tar.gz", nuVersion) + default: + a.Logger.Debugln("GetNushell(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS) + return + } + case "linux": + switch runtime.GOARCH { + case "amd64": + // https://github.com/nushell/nushell/releases/download/0.87.0/nu-0.87.0-x86_64-linux-musl-full.tar.gz + assetName = fmt.Sprintf("nu-%s-x86_64-linux-musl-full.tar.gz", nuVersion) + case "arm64": + // https://github.com/nushell/nushell/releases/download/0.87.0/nu-0.87.0-aarch64-linux-gnu-full.tar.gz + assetName = fmt.Sprintf("nu-%s-aarch64-linux-gnu-full.tar.gz", nuVersion) + default: + a.Logger.Debugln("GetNushell(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS) + return + } + default: + a.Logger.Debugln("GetNushell(): Unsupported OS:", runtime.GOOS) + return + } + url = fmt.Sprintf("https://github.com/nushell/nushell/releases/download/%s/%s", nuVersion, assetName) + a.Logger.Debugln("GetNushell(): Nu download url:", url) + + tmpDir, err := os.MkdirTemp("", "trmm") + if err != nil { + a.Logger.Errorln("GetNushell(): Error creating temp directory:", err) + return + } + defer func(path string) { + err := os.RemoveAll(path) + if err != nil { + a.Logger.Errorln("GetNushell(): Error removing temp directory:", err) + } + }(tmpDir) + + tmpAssetName := filepath.Join(tmpDir, assetName) + a.Logger.Debugln("GetNushell(): tmpAssetName:", tmpAssetName) + + rClient := resty.New() + rClient.SetTimeout(20 * time.Minute) + rClient.SetRetryCount(10) + rClient.SetRetryWaitTime(1 * time.Minute) + rClient.SetRetryMaxWaitTime(15 * time.Minute) + if len(a.Proxy) > 0 { + rClient.SetProxy(a.Proxy) + } + + r, err := rClient.R().SetOutput(tmpAssetName).Get(url) + if err != nil { + a.Logger.Errorln("GetNushell(): Unable to download nu from github.", err) + return + } + if r.IsError() { + a.Logger.Errorln("GetNushell(): Unable to download nu from github. Status code", r.StatusCode()) + return + } + + targzDirName, err = a.ExtractTarGz(tmpAssetName, tmpDir) + if err != nil { + a.Logger.Errorln("GetNushell(): Failed to extract downloaded tar.gz file:", err) + return + } + + err = copyFile(path.Join(tmpDir, targzDirName, "nu"), a.NuBin) + if err != nil { + a.Logger.Errorln("GetNushell(): Failed to copy nu file to install dir:", err) + return + } + + err = os.Chmod(a.NuBin, 0755) + if err != nil { + a.Logger.Errorln("GetNushell(): Failed to chmod nu binary:", err) + return + } + +} + +// GetDeno will download deno from GitHub and install (copy) it to nixAgentBinDir +func (a *Agent) GetDeno(force bool) { + if trmm.FileExists(a.DenoBin) { + if force { + err := os.Remove(a.DenoBin) + if err != nil { + a.Logger.Errorln("GetDeno(): Error removing deno binary:", err) + return + } + } else { + return + } + } + + if !trmm.FileExists(nixAgentBinDir) { + err := os.MkdirAll(nixAgentBinDir, 0755) + if err != nil { + a.Logger.Errorln("GetDeno(): Error creating nixAgentBinDir:", err) + return + } + } + + var ( + assetName string + url string + ) + + switch runtime.GOOS { + case "darwin": + switch runtime.GOARCH { + case "arm64": + // https://github.com/denoland/deno/releases/download/v1.38.2/deno-aarch64-apple-darwin.zip + assetName = fmt.Sprintf("deno-aarch64-apple-darwin.zip") + case "amd64": + // https://github.com/denoland/deno/releases/download/v1.38.2/deno-x86_64-apple-darwin.zip + assetName = fmt.Sprintf("deno-x86_64-apple-darwin.zip") + default: + a.Logger.Debugln("GetDeno(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS) + return + } + case "linux": + switch runtime.GOARCH { + case "amd64": + // https://github.com/denoland/deno/releases/download/v1.38.2/deno-x86_64-unknown-linux-gnu.zip + assetName = fmt.Sprintf("deno-x86_64-unknown-linux-gnu.zip") + default: + a.Logger.Debugln("GetDeno(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS) + return + } + default: + a.Logger.Debugln("GetDeno(): Unsupported OS:", runtime.GOOS) + return + } + url = fmt.Sprintf("https://github.com/denoland/deno/releases/download/%s/%s", denoVersion, assetName) + a.Logger.Debugln("GetDeno(): Deno download url:", url) + + tmpDir, err := os.MkdirTemp("", "trmm") + if err != nil { + a.Logger.Errorln("GetDeno(): Error creating temp directory:", err) + return + } + defer func(path string) { + err := os.RemoveAll(path) + if err != nil { + a.Logger.Errorln("GetDeno(): Error removing temp directory:", err) + } + }(tmpDir) + + tmpAssetName := filepath.Join(tmpDir, assetName) + a.Logger.Debugln("GetDeno(): tmpAssetName:", tmpAssetName) + + if !trmm.FileExists(nixAgentBinDir) { + err = os.MkdirAll(nixAgentBinDir, 0755) + if err != nil { + a.Logger.Errorln("GetDeno(): Error creating nixAgentBinDir:", err) + return + } + } + + rClient := resty.New() + rClient.SetTimeout(20 * time.Minute) + rClient.SetRetryCount(10) + rClient.SetRetryWaitTime(1 * time.Minute) + rClient.SetRetryMaxWaitTime(15 * time.Minute) + if len(a.Proxy) > 0 { + rClient.SetProxy(a.Proxy) + } + + r, err := rClient.R().SetOutput(tmpAssetName).Get(url) + if err != nil { + a.Logger.Errorln("GetDeno(): Unable to download deno from github.", err) + return + } + if r.IsError() { + a.Logger.Errorln("GetDeno(): Unable to download deno from github. Status code", r.StatusCode()) + return + } + + err = Unzip(tmpAssetName, tmpDir) + if err != nil { + a.Logger.Errorln("GetDeno(): Failed to unzip downloaded zip file:", err) + return + } + + err = copyFile(path.Join(tmpDir, "deno"), a.DenoBin) + if err != nil { + a.Logger.Errorln("GetDeno(): Failed to copy deno file to install dir:", err) + return + } + + err = os.Chmod(a.DenoBin, 0755) + if err != nil { + a.Logger.Errorln("GetDeno(): Failed to chmod deno binary:", err) + return + } + +} + type SchedTask struct{ Name string } func (a *Agent) PatchMgmnt(enable bool) error { return nil } diff --git a/agent/agent_windows.go b/agent/agent_windows.go index d9f6678..c61a29a 100644 --- a/agent/agent_windows.go +++ b/agent/agent_windows.go @@ -19,6 +19,7 @@ import ( "fmt" "os" "os/exec" + "path" "path/filepath" "runtime" "strconv" @@ -156,9 +157,41 @@ func (a *Agent) RunScript(code string, shell string, args []string, timeout int, case "cmd": exe = tmpfn.Name() case "nushell": - exe = tmpfn.Name() + exe = a.NuBin + cmdArgs = []string{"--no-config-file", tmpfn.Name()} + if !trmm.FileExists(a.NuBin) { + a.Logger.Errorln("RunScript(): Executable does not exist. Install Nu and try again:", a.NuBin) + err := errors.New("File Not Found: " + a.NuBin) + return "", err.Error(), 85, err + } case "deno": - exe = tmpfn.Name() + exe = a.DenoBin + cmdArgs = []string{"run", "--no-prompt"} + if !trmm.FileExists(a.DenoBin) { + a.Logger.Errorln("RunScript(): Executable does not exist. Install deno and try again:", a.DenoBin) + err := errors.New("File Not Found: " + a.DenoBin) + return "", err.Error(), 85, err + } + + // Search the environment variables for DENO_PERMISSIONS and use that to set permissions for the script. + // https://docs.deno.com/runtime/manual/basics/permissions#permissions-list + // DENO_PERMISSIONS is not an official environment variable. + // https://docs.deno.com/runtime/manual/basics/env_variables + for i, v := range envVars { + if strings.HasPrefix(v, "DENO_PERMISSIONS=") { + permissions := strings.Split(v, "=")[1] + cmdArgs = append(cmdArgs, strings.Split(permissions, " ")...) + // Remove the DENO_PERMISSIONS variable from the environment variables slice. + // It's possible more variables may exist with the same prefix. + envVars = append(envVars[:i], envVars[i+1:]...) + break + } + } + + // Can't append a variadic slice after a string arg. + // https://pkg.go.dev/builtin#append + cmdArgs = append(cmdArgs, tmpfn.Name()) + } if len(args) > 0 { @@ -846,6 +879,204 @@ func (a *Agent) GetPython(force bool) { } } +// GetNushell will download nushell from GitHub and install (copy) it to ProgramDir\bin, where ProgramDir is +// initialized to C:\Program Files\TacticalAgent +func (a *Agent) GetNushell(force bool) { + if trmm.FileExists(a.NuBin) { + if force { + err := os.Remove(a.NuBin) + if err != nil { + a.Logger.Errorln("GetNushell(): Error removing nu binary:", err) + return + } + } else { + return + } + } + + programBinDir := path.Join(a.ProgramDir, "bin") + if !trmm.FileExists(programBinDir) { + err := os.MkdirAll(programBinDir, 0755) + if err != nil { + a.Logger.Errorln("GetNushell(): Error creating Program Files bin folder:", err) + return + } + } + + var ( + assetName string + url string + ) + + switch runtime.GOOS { + case "windows": + switch runtime.GOARCH { + case "amd64": + // https://github.com/nushell/nushell/releases/download/0.87.0/nu-0.87.0-x86_64-windows-msvc-full.zip + assetName = fmt.Sprintf("nu-%s-x86_64-windows-msvc-full.zip", nuVersion) + case "arm64": + // https://github.com/nushell/nushell/releases/download/0.87.0/nu-0.87.0-aarch64-windows-msvc-full.zip + assetName = fmt.Sprintf("nu-%s-aarch64-windows-msvc-full.zip", nuVersion) + default: + a.Logger.Debugln("GetNushell(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS) + return + } + default: + a.Logger.Debugln("GetNushell(): Unsupported OS:", runtime.GOOS) + return + } + url = fmt.Sprintf("https://github.com/nushell/nushell/releases/download/%s/%s", nuVersion, assetName) + a.Logger.Debugln("GetNushell(): Nu download url:", url) + + tmpDir, err := os.MkdirTemp("", "trmm") + if err != nil { + a.Logger.Errorln("GetNushell(): Error creating temp directory:", err) + return + } + defer func(path string) { + err := os.RemoveAll(path) + if err != nil { + a.Logger.Errorln("GetNushell(): Error removing temp directory:", err) + } + }(tmpDir) + + tmpAssetName := filepath.Join(tmpDir, assetName) + a.Logger.Debugln("GetNushell(): tmpAssetName:", tmpAssetName) + + rClient := resty.New() + rClient.SetTimeout(20 * time.Minute) + rClient.SetRetryCount(10) + rClient.SetRetryWaitTime(1 * time.Minute) + rClient.SetRetryMaxWaitTime(15 * time.Minute) + if len(a.Proxy) > 0 { + rClient.SetProxy(a.Proxy) + } + + r, err := rClient.R().SetOutput(tmpAssetName).Get(url) + if err != nil { + a.Logger.Errorln("GetNushell(): Unable to download nu from github.", err) + return + } + if r.IsError() { + a.Logger.Errorln("GetNushell(): Unable to download nu from github. Status code", r.StatusCode()) + return + } + + err = Unzip(tmpAssetName, tmpDir) + if err != nil { + a.Logger.Errorln("GetNushell(): Failed to unzip downloaded zip file:", err) + return + } + + err = copyFile(path.Join(tmpDir, "nu.exe"), a.NuBin) + if err != nil { + a.Logger.Errorln("GetNushell(): Failed to copy nu.exe file to install dir:", err) + return + } + +} + +// GetDeno will download deno from GitHub and install (copy) it to nixAgentBinDir +func (a *Agent) GetDeno(force bool) { + if trmm.FileExists(a.DenoBin) { + if force { + err := os.Remove(a.DenoBin) + if err != nil { + a.Logger.Errorln("GetDeno(): Error removing deno binary:", err) + return + } + } else { + return + } + } + + programBinDir := path.Join(a.ProgramDir, "bin") + if !trmm.FileExists(programBinDir) { + err := os.MkdirAll(programBinDir, 0755) + if err != nil { + a.Logger.Errorln("GetDeno(): Error creating Program Files bin folder:", err) + return + } + } + + var ( + assetName string + url string + ) + + switch runtime.GOOS { + case "windows": + switch runtime.GOARCH { + case "amd64": + // https://github.com/denoland/deno/releases/download/v1.38.2/deno-x86_64-pc-windows-msvc.zip + assetName = fmt.Sprintf("deno-x86_64-pc-windows-msvc.zip") + default: + a.Logger.Debugln("GetDeno(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS) + return + } + default: + a.Logger.Debugln("GetDeno(): Unsupported OS:", runtime.GOOS) + return + } + url = fmt.Sprintf("https://github.com/denoland/deno/releases/download/%s/%s", denoVersion, assetName) + a.Logger.Debugln("GetDeno(): Deno download url:", url) + + tmpDir, err := os.MkdirTemp("", "trmm") + if err != nil { + a.Logger.Errorln("GetDeno(): Error creating temp directory:", err) + return + } + defer func(path string) { + err := os.RemoveAll(path) + if err != nil { + a.Logger.Errorln("GetDeno(): Error removing temp directory:", err) + } + }(tmpDir) + + tmpAssetName := filepath.Join(tmpDir, assetName) + a.Logger.Debugln("GetDeno(): tmpAssetName:", tmpAssetName) + + if !trmm.FileExists(nixAgentBinDir) { + err = os.MkdirAll(nixAgentBinDir, 0755) + if err != nil { + a.Logger.Errorln("GetDeno(): Error creating nixAgentBinDir:", err) + return + } + } + + rClient := resty.New() + rClient.SetTimeout(20 * time.Minute) + rClient.SetRetryCount(10) + rClient.SetRetryWaitTime(1 * time.Minute) + rClient.SetRetryMaxWaitTime(15 * time.Minute) + if len(a.Proxy) > 0 { + rClient.SetProxy(a.Proxy) + } + + r, err := rClient.R().SetOutput(tmpAssetName).Get(url) + if err != nil { + a.Logger.Errorln("GetDeno(): Unable to download deno from github.", err) + return + } + if r.IsError() { + a.Logger.Errorln("GetDeno(): Unable to download deno from github. Status code", r.StatusCode()) + return + } + + err = Unzip(tmpAssetName, tmpDir) + if err != nil { + a.Logger.Errorln("GetDeno(): Failed to unzip downloaded zip file:", err) + return + } + + err = copyFile(path.Join(tmpDir, "deno.exe"), a.DenoBin) + if err != nil { + a.Logger.Errorln("GetDeno(): Failed to copy deno.exe file to install dir:", err) + return + } + +} + func (a *Agent) RecoverMesh() { a.Logger.Infoln("Attempting mesh recovery") defer CMD("net", []string{"start", a.MeshSVC}, 60, false) diff --git a/agent/install.go b/agent/install.go index 1d6c75a..4186754 100644 --- a/agent/install.go +++ b/agent/install.go @@ -17,6 +17,7 @@ import ( "io" "net/url" "os" + "path" "path/filepath" "regexp" "runtime" @@ -258,8 +259,13 @@ func (a *Agent) Install(i *Installer) { // check in once a.DoNatsCheckIn() + if runtime.GOOS == "linux" { + // Used for Nushell and Deno binaries + os.MkdirAll(nixAgentBinDir, 0755) + } + if runtime.GOOS == "darwin" { - os.MkdirAll(nixAgentDir, 0755) + os.MkdirAll(nixAgentBinDir, 0755) self, _ := os.Executable() copyFile(self, nixAgentBin) os.Chmod(nixAgentBin, 0755) @@ -300,6 +306,8 @@ func (a *Agent) Install(i *Installer) { } if runtime.GOOS == "windows" { + os.MkdirAll(path.Join(a.ProgramDir, "bin"), 0755) + // send software api a.SendSoftware() @@ -341,7 +349,7 @@ func (a *Agent) Install(i *Installer) { } } - a.installerMsg("Installation was successfull!\nAllow a few minutes for the agent to properly display in the RMM", "info", i.Silent) + a.installerMsg("Installation was successful!\nAllow a few minutes for the agent to properly display in the RMM", "info", i.Silent) } func copyFile(src, dst string) error { diff --git a/agent/rpc.go b/agent/rpc.go index 8ecae5c..03b1508 100644 --- a/agent/rpc.go +++ b/agent/rpc.go @@ -437,6 +437,10 @@ func (a *Agent) RunRPC() { }() case "installpython": go a.GetPython(true) + case "installnushell": + go a.GetNushell(true) + case "installdeno": + go a.GetDeno(true) case "installchoco": go a.InstallChoco() case "installwithchoco": diff --git a/agent/svc.go b/agent/svc.go index e7f03ea..b04bb9d 100644 --- a/agent/svc.go +++ b/agent/svc.go @@ -49,6 +49,9 @@ func (a *Agent) AgentSvc(nc *nats.Conn) { a.Logger.Errorln("AgentSvc() createWinTempDir():", err) } } + a.GetNushell(false) + a.GetDeno(false) + a.RunMigrations() sleepDelay := randRange(7, 25) diff --git a/agent/utils.go b/agent/utils.go index 6eacaca..f915fc5 100644 --- a/agent/utils.go +++ b/agent/utils.go @@ -12,8 +12,10 @@ https://license.tacticalrmm.com package agent import ( + "archive/tar" "archive/zip" "bytes" + "compress/gzip" "fmt" "io" "math" @@ -21,6 +23,7 @@ import ( "net" "os" "os/exec" + "path" "path/filepath" "runtime" goDebug "runtime/debug" @@ -280,6 +283,70 @@ func Unzip(src, dest string) error { return nil } +// ExtractTarGz extracts a tar.gz file to the specified directory. +// Returns the extracted directory name. +// https://stackoverflow.com/questions/57639648/how-to-decompress-tar-gz-file-in-go +func (a *Agent) ExtractTarGz(targz string, destDir string) (extractedDir string, err error) { + gzipStream, err := os.Open(targz) + if err != nil { + a.Logger.Errorln("ExtractTarGz(): Open() failed:", err.Error()) + return "", err + } + + uncompressedStream, err := gzip.NewReader(gzipStream) + if err != nil { + a.Logger.Errorln("ExtractTarGz(): NewReader() failed:", err.Error()) + return "", err + } + + extractedDir = "" + tarReader := tar.NewReader(uncompressedStream) + for true { + header, err := tarReader.Next() + + if err == io.EOF { + break + } + + if err != nil { + a.Logger.Errorln("ExtractTarGz(): Next() failed:", err.Error()) + return "", err + } + + switch header.Typeflag { + case tar.TypeDir: + if err := os.MkdirAll(path.Join(destDir, header.Name), 0755); err != nil { + a.Logger.Errorln("ExtractTarGz(): Mkdir() failed:", err.Error()) + return "", err + } + if extractedDir == "" { + extractedDir = header.Name + } + case tar.TypeReg: + outFile, err := os.Create(path.Join(destDir, header.Name)) + if err != nil { + a.Logger.Errorln("ExtractTarGz(): Create() failed:", err.Error()) + return "", err + } + if _, err := io.Copy(outFile, tarReader); err != nil { + a.Logger.Errorln("ExtractTarGz(): Copy() failed:", err.Error()) + return "", err + } + err = outFile.Close() + if err != nil { + a.Logger.Errorln("ExtractTarGz(): Close() failed:", err.Error()) + return "", err + } + + default: + a.Logger.Errorln("ExtractTarGz(): Unknown type: %s in %s", header.Typeflag, header.Name) + return "", err + } + + } + return extractedDir, nil +} + // https://yourbasic.org/golang/formatting-byte-size-to-human-readable-format/ func ByteCountSI(b uint64) string { const unit = 1024 diff --git a/main.go b/main.go index cc302ca..0d46a62 100644 --- a/main.go +++ b/main.go @@ -117,6 +117,10 @@ func main() { fmt.Println(a.PublicIP()) case "getpython": a.GetPython(true) + case "getdeno": + a.GetDeno(true) + case "getnushell": + a.GetNushell(true) case "runmigrations": a.RunMigrations() case "recovermesh": From 2afdfd7ab88083b27d42898dd2ffe1adf3868c4b Mon Sep 17 00:00:00 2001 From: David Randall Date: Sun, 3 Dec 2023 23:14:27 -0500 Subject: [PATCH 3/3] Add: Server variables are opt-out by default - Pull the Nushell and Deno versions from the server. - Support downloading Nushell and Deno from a url (not GitHUb). - Add support for nu config.nu and env.nu files. - Add support for default Deno permissions. --- agent/agent.go | 3 +- agent/agent_unix.go | 328 ++++++++++++++++++++++++++--------------- agent/agent_windows.go | 242 ++++++++++++++++++++---------- agent/checks.go | 2 +- agent/choco_windows.go | 2 +- agent/embed_darwin.go | 2 +- agent/rpc.go | 42 +++--- agent/svc.go | 56 +++++-- agent/tasks_windows.go | 4 +- main.go | 8 +- shared/types.go | 68 +++++---- 11 files changed, 490 insertions(+), 267 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index cd3c895..c0c947c 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -89,12 +89,11 @@ const ( nixMeshDir = "/opt/tacticalmesh" nixAgentBin = nixAgentDir + "/tacticalagent" nixAgentBinDir = nixAgentDir + "/bin" + nixAgentEtcDir = nixAgentDir + "/etc" nixMeshAgentBin = nixMeshDir + "/meshagent" macPlistPath = "/Library/LaunchDaemons/tacticalagent.plist" macPlistName = "tacticalagent" defaultMacMeshSvcDir = "/usr/local/mesh_services" - nuVersion = "0.87.0" - denoVersion = "v1.38.2" ) var defaultWinTmpDir = filepath.Join(os.Getenv("PROGRAMDATA"), "TacticalRMM") diff --git a/agent/agent_unix.go b/agent/agent_unix.go index aebd808..5de2595 100644 --- a/agent/agent_unix.go +++ b/agent/agent_unix.go @@ -167,7 +167,7 @@ func NewAgentConfig() *rmm.AgentConfig { return ret } -func (a *Agent) RunScript(code string, shell string, args []string, timeout int, runasuser bool, envVars []string) (stdout, stderr string, exitcode int, e error) { +func (a *Agent) RunScript(code string, shell string, args []string, timeout int, runasuser bool, envVars []string, nushellEnableConfig bool, denoDefaultPermissions string) (stdout, stderr string, exitcode int, e error) { code = removeWinNewLines(code) content := []byte(code) @@ -197,12 +197,21 @@ func (a *Agent) RunScript(code string, shell string, args []string, timeout int, opts.IsScript = true switch shell { case "nushell": + var nushellArgs []string + if nushellEnableConfig { + nushellArgs = []string{ + "--config", + path.Join(nixAgentEtcDir, "nushell", "config.nu"), + "--env-config", + path.Join(nixAgentEtcDir, "nushell", "env.nu"), + } + } else { + nushellArgs = []string{"--no-config-file"} + } opts.Shell = a.NuBin - opts.Args = append([]string{ - "--no-config-file", - f.Name(), - }, - args...) + opts.Args = nushellArgs + opts.Args = append(opts.Args, f.Name()) + opts.Args = append(opts.Args, args...) if !trmm.FileExists(a.NuBin) { a.Logger.Errorln("RunScript(): Executable does not exist. Install Nu and try again:", a.NuBin) err := errors.New("File Not Found: " + a.NuBin) @@ -225,6 +234,8 @@ func (a *Agent) RunScript(code string, shell string, args []string, timeout int, // https://docs.deno.com/runtime/manual/basics/permissions#permissions-list // DENO_PERMISSIONS is not an official environment variable. // https://docs.deno.com/runtime/manual/basics/env_variables + // DENO_DEFAULT_PERMISSIONS is used if not found in the environment variables. + found := false for i, v := range envVars { if strings.HasPrefix(v, "DENO_PERMISSIONS=") { permissions := strings.Split(v, "=")[1] @@ -232,9 +243,13 @@ func (a *Agent) RunScript(code string, shell string, args []string, timeout int, // Remove the DENO_PERMISSIONS variable from the environment variables slice. // It's possible more variables may exist with the same prefix. envVars = append(envVars[:i], envVars[i+1:]...) + found = true break } } + if !found && denoDefaultPermissions != "" { + opts.Args = append(opts.Args, strings.Split(denoDefaultPermissions, " ")...) + } // Can't append a variadic slice after a string arg. // https://pkg.go.dev/builtin#append @@ -536,30 +551,19 @@ func (a *Agent) GetWMIInfo() map[string]interface{} { return wmiInfo } -// windows only below TODO add into stub file -func (a *Agent) GetAgentCheckInConfig(ret AgentCheckInConfig) AgentCheckInConfig { - return ret -} - -func (a *Agent) PlatVer() (string, error) { return "", nil } - -func (a *Agent) SendSoftware() {} - -func (a *Agent) UninstallCleanup() {} - -func (a *Agent) RunMigrations() {} - -func GetServiceStatus(name string) (string, error) { return "", nil } - -func (a *Agent) GetPython(force bool) {} +// InstallNushell will download nushell from GitHub and install (copy) it to nixAgentBinDir +func (a *Agent) InstallNushell(force bool) { + conf := a.GetAgentCheckInConfig(a.GetCheckInConfFromAPI()) + if !conf.InstallNushell { + return + } -// GetNushell will download nushell from GitHub and install (copy) it to nixAgentBinDir -func (a *Agent) GetNushell(force bool) { if trmm.FileExists(a.NuBin) { if force { + a.Logger.Debugln(a.NuBin, "InstallNushell(): Forced install. Removing nu binary.") err := os.Remove(a.NuBin) if err != nil { - a.Logger.Errorln("GetNushell(): Error removing nu binary:", err) + a.Logger.Errorln("InstallNushell(): Error removing nu binary:", err) return } } else { @@ -570,60 +574,106 @@ func (a *Agent) GetNushell(force bool) { if !trmm.FileExists(nixAgentBinDir) { err := os.MkdirAll(nixAgentBinDir, 0755) if err != nil { - a.Logger.Errorln("GetNushell(): Error creating nixAgentBinDir:", err) + a.Logger.Errorln("InstallNushell(): Error creating nixAgentBinDir:", err) return } } + if conf.NushellEnableConfig { + // Create 0-byte config files for Nushell + nushellPath := path.Join(nixAgentEtcDir, "nushell") + nushellConfig := path.Join(nushellPath, "config.nu") + nushellEnv := path.Join(nushellPath, "env.nu") + if !trmm.FileExists(nushellPath) { + err := os.MkdirAll(nushellPath, 0755) + if err != nil { + a.Logger.Errorln("InstallNushell(): Error creating nixAgentEtcDir/nushell:", err) + return + } + } + + if !trmm.FileExists(nushellConfig) { + _, err := os.Create(nushellConfig) + if err != nil { + a.Logger.Errorln("InstallNushell(): Error creating nushell config.nu:", err) + return + } + err = os.Chmod(nushellConfig, 0744) + if err != nil { + a.Logger.Errorln("InstallNushell(): Error changing permissions for nushell config.nu:", err) + return + } + } + if !trmm.FileExists(nushellEnv) { + _, err := os.Create(nushellEnv) + if err != nil { + a.Logger.Errorln("InstallNushell(): Error creating nushell env.nu:", err) + return + } + err = os.Chmod(nushellEnv, 0744) + if err != nil { + a.Logger.Errorln("InstallNushell(): Error changing permissions for nushell env.nu:", err) + return + } + } + } + var ( assetName string url string targzDirName string ) - switch runtime.GOOS { - case "darwin": - switch runtime.GOARCH { - case "arm64": - // https://github.com/nushell/nushell/releases/download/0.87.0/nu-0.87.0-aarch64-darwin-full.tar.gz - assetName = fmt.Sprintf("nu-%s-aarch64-darwin-full.tar.gz", nuVersion) - default: - a.Logger.Debugln("GetNushell(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS) - return - } - case "linux": - switch runtime.GOARCH { - case "amd64": - // https://github.com/nushell/nushell/releases/download/0.87.0/nu-0.87.0-x86_64-linux-musl-full.tar.gz - assetName = fmt.Sprintf("nu-%s-x86_64-linux-musl-full.tar.gz", nuVersion) - case "arm64": - // https://github.com/nushell/nushell/releases/download/0.87.0/nu-0.87.0-aarch64-linux-gnu-full.tar.gz - assetName = fmt.Sprintf("nu-%s-aarch64-linux-gnu-full.tar.gz", nuVersion) + if conf.InstallNushellUrl != "" { + url = conf.InstallNushellUrl + url = strings.Replace(url, "{OS}", runtime.GOOS, -1) + url = strings.Replace(url, "{ARCH}", runtime.GOARCH, -1) + url = strings.Replace(url, "{VERSION}", conf.InstallNushellVersion, -1) + } else { + switch runtime.GOOS { + case "darwin": + switch runtime.GOARCH { + case "arm64": + // https://github.com/nushell/nushell/releases/download/0.87.0/nu-0.87.0-aarch64-darwin-full.tar.gz + assetName = fmt.Sprintf("nu-%s-aarch64-darwin-full.tar.gz", conf.InstallNushellVersion) + default: + a.Logger.Debugln("InstallNushell(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS) + return + } + case "linux": + switch runtime.GOARCH { + case "amd64": + // https://github.com/nushell/nushell/releases/download/0.87.0/nu-0.87.0-x86_64-linux-musl-full.tar.gz + assetName = fmt.Sprintf("nu-%s-x86_64-linux-musl-full.tar.gz", conf.InstallNushellVersion) + case "arm64": + // https://github.com/nushell/nushell/releases/download/0.87.0/nu-0.87.0-aarch64-linux-gnu-full.tar.gz + assetName = fmt.Sprintf("nu-%s-aarch64-linux-gnu-full.tar.gz", conf.InstallNushellVersion) + default: + a.Logger.Debugln("InstallNushell(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS) + return + } default: - a.Logger.Debugln("GetNushell(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS) + a.Logger.Debugln("InstallNushell(): Unsupported OS:", runtime.GOOS) return } - default: - a.Logger.Debugln("GetNushell(): Unsupported OS:", runtime.GOOS) - return + url = fmt.Sprintf("https://github.com/nushell/nushell/releases/download/%s/%s", conf.InstallNushellVersion, assetName) } - url = fmt.Sprintf("https://github.com/nushell/nushell/releases/download/%s/%s", nuVersion, assetName) - a.Logger.Debugln("GetNushell(): Nu download url:", url) + a.Logger.Debugln("InstallNushell(): Nu download url:", url) tmpDir, err := os.MkdirTemp("", "trmm") if err != nil { - a.Logger.Errorln("GetNushell(): Error creating temp directory:", err) + a.Logger.Errorln("InstallNushell(): Error creating temp directory:", err) return } defer func(path string) { err := os.RemoveAll(path) if err != nil { - a.Logger.Errorln("GetNushell(): Error removing temp directory:", err) + a.Logger.Errorln("InstallNushell(): Error removing temp directory:", err) } }(tmpDir) tmpAssetName := filepath.Join(tmpDir, assetName) - a.Logger.Debugln("GetNushell(): tmpAssetName:", tmpAssetName) + a.Logger.Debugln("InstallNushell(): tmpAssetName:", tmpAssetName) rClient := resty.New() rClient.SetTimeout(20 * time.Minute) @@ -636,41 +686,57 @@ func (a *Agent) GetNushell(force bool) { r, err := rClient.R().SetOutput(tmpAssetName).Get(url) if err != nil { - a.Logger.Errorln("GetNushell(): Unable to download nu from github.", err) + a.Logger.Errorln("InstallNushell(): Unable to download nu:", err) return } if r.IsError() { - a.Logger.Errorln("GetNushell(): Unable to download nu from github. Status code", r.StatusCode()) + a.Logger.Errorln("InstallNushell(): Unable to download nu. Status code:", r.StatusCode()) return } - targzDirName, err = a.ExtractTarGz(tmpAssetName, tmpDir) - if err != nil { - a.Logger.Errorln("GetNushell(): Failed to extract downloaded tar.gz file:", err) - return - } + if conf.InstallNushellUrl != "" { + // InstallNushellUrl is not compressed. + err = copyFile(path.Join(tmpDir, tmpAssetName), a.NuBin) + if err != nil { + a.Logger.Errorln("InstallNushell(): Failed to copy nu file to install dir:", err) + return + } + } else { + // GitHub asset is tar.gz compressed. + targzDirName, err = a.ExtractTarGz(tmpAssetName, tmpDir) + if err != nil { + a.Logger.Errorln("InstallNushell(): Failed to extract downloaded tar.gz file:", err) + return + } - err = copyFile(path.Join(tmpDir, targzDirName, "nu"), a.NuBin) - if err != nil { - a.Logger.Errorln("GetNushell(): Failed to copy nu file to install dir:", err) - return + err = copyFile(path.Join(tmpDir, targzDirName, "nu"), a.NuBin) + if err != nil { + a.Logger.Errorln("InstallNushell(): Failed to copy nu file to install dir:", err) + return + } } err = os.Chmod(a.NuBin, 0755) if err != nil { - a.Logger.Errorln("GetNushell(): Failed to chmod nu binary:", err) + a.Logger.Errorln("InstallNushell(): Failed to chmod nu binary:", err) return } } -// GetDeno will download deno from GitHub and install (copy) it to nixAgentBinDir -func (a *Agent) GetDeno(force bool) { +// InstallDeno will download deno from GitHub and install (copy) it to nixAgentBinDir +func (a *Agent) InstallDeno(force bool) { + conf := a.GetAgentCheckInConfig(a.GetCheckInConfFromAPI()) + if !conf.InstallDeno { + return + } + if trmm.FileExists(a.DenoBin) { if force { + a.Logger.Debugln(a.NuBin, "InstallDeno(): Forced install. Removing deno binary.") err := os.Remove(a.DenoBin) if err != nil { - a.Logger.Errorln("GetDeno(): Error removing deno binary:", err) + a.Logger.Errorln("InstallDeno(): Error removing deno binary:", err) return } } else { @@ -681,7 +747,7 @@ func (a *Agent) GetDeno(force bool) { if !trmm.FileExists(nixAgentBinDir) { err := os.MkdirAll(nixAgentBinDir, 0755) if err != nil { - a.Logger.Errorln("GetDeno(): Error creating nixAgentBinDir:", err) + a.Logger.Errorln("InstallDeno(): Error creating nixAgentBinDir:", err) return } } @@ -691,57 +757,56 @@ func (a *Agent) GetDeno(force bool) { url string ) - switch runtime.GOOS { - case "darwin": - switch runtime.GOARCH { - case "arm64": - // https://github.com/denoland/deno/releases/download/v1.38.2/deno-aarch64-apple-darwin.zip - assetName = fmt.Sprintf("deno-aarch64-apple-darwin.zip") - case "amd64": - // https://github.com/denoland/deno/releases/download/v1.38.2/deno-x86_64-apple-darwin.zip - assetName = fmt.Sprintf("deno-x86_64-apple-darwin.zip") - default: - a.Logger.Debugln("GetDeno(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS) - return - } - case "linux": - switch runtime.GOARCH { - case "amd64": - // https://github.com/denoland/deno/releases/download/v1.38.2/deno-x86_64-unknown-linux-gnu.zip - assetName = fmt.Sprintf("deno-x86_64-unknown-linux-gnu.zip") + if conf.InstallDenoUrl != "" { + url = conf.InstallDenoUrl + url = strings.Replace(url, "{OS}", runtime.GOOS, -1) + url = strings.Replace(url, "{ARCH}", runtime.GOARCH, -1) + url = strings.Replace(url, "{VERSION}", conf.InstallDenoVersion, -1) + } else { + switch runtime.GOOS { + case "darwin": + switch runtime.GOARCH { + case "arm64": + // https://github.com/denoland/deno/releases/download/v1.38.2/deno-aarch64-apple-darwin.zip + assetName = fmt.Sprintf("deno-aarch64-apple-darwin.zip") + case "amd64": + // https://github.com/denoland/deno/releases/download/v1.38.2/deno-x86_64-apple-darwin.zip + assetName = fmt.Sprintf("deno-x86_64-apple-darwin.zip") + default: + a.Logger.Debugln("InstallDeno(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS) + return + } + case "linux": + switch runtime.GOARCH { + case "amd64": + // https://github.com/denoland/deno/releases/download/v1.38.2/deno-x86_64-unknown-linux-gnu.zip + assetName = fmt.Sprintf("deno-x86_64-unknown-linux-gnu.zip") + default: + a.Logger.Debugln("InstallDeno(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS) + return + } default: - a.Logger.Debugln("GetDeno(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS) + a.Logger.Debugln("InstallDeno(): Unsupported OS:", runtime.GOOS) return } - default: - a.Logger.Debugln("GetDeno(): Unsupported OS:", runtime.GOOS) - return + url = fmt.Sprintf("https://github.com/denoland/deno/releases/download/%s/%s", conf.InstallDenoVersion, assetName) } - url = fmt.Sprintf("https://github.com/denoland/deno/releases/download/%s/%s", denoVersion, assetName) - a.Logger.Debugln("GetDeno(): Deno download url:", url) + a.Logger.Debugln("InstallDeno(): Deno download url:", url) tmpDir, err := os.MkdirTemp("", "trmm") if err != nil { - a.Logger.Errorln("GetDeno(): Error creating temp directory:", err) + a.Logger.Errorln("InstallDeno(): Error creating temp directory:", err) return } defer func(path string) { err := os.RemoveAll(path) if err != nil { - a.Logger.Errorln("GetDeno(): Error removing temp directory:", err) + a.Logger.Errorln("InstallDeno(): Error removing temp directory:", err) } }(tmpDir) tmpAssetName := filepath.Join(tmpDir, assetName) - a.Logger.Debugln("GetDeno(): tmpAssetName:", tmpAssetName) - - if !trmm.FileExists(nixAgentBinDir) { - err = os.MkdirAll(nixAgentBinDir, 0755) - if err != nil { - a.Logger.Errorln("GetDeno(): Error creating nixAgentBinDir:", err) - return - } - } + a.Logger.Debugln("InstallDeno(): tmpAssetName:", tmpAssetName) rClient := resty.New() rClient.SetTimeout(20 * time.Minute) @@ -754,34 +819,65 @@ func (a *Agent) GetDeno(force bool) { r, err := rClient.R().SetOutput(tmpAssetName).Get(url) if err != nil { - a.Logger.Errorln("GetDeno(): Unable to download deno from github.", err) + a.Logger.Errorln("InstallDeno(): Unable to download deno:", err) return } if r.IsError() { - a.Logger.Errorln("GetDeno(): Unable to download deno from github. Status code", r.StatusCode()) + a.Logger.Errorln("InstallDeno(): Unable to download deno. Status code:", r.StatusCode()) return } - err = Unzip(tmpAssetName, tmpDir) - if err != nil { - a.Logger.Errorln("GetDeno(): Failed to unzip downloaded zip file:", err) - return - } + if conf.InstallDenoUrl != "" { + // InstallDenoUrl is not compressed. + err = copyFile(path.Join(tmpDir, tmpAssetName), a.DenoBin) + if err != nil { + a.Logger.Errorln("InstallDeno(): Failed to copy deno file to install dir:", err) + return + } + } else { + // GitHub asset is zip compressed. + err = Unzip(tmpAssetName, tmpDir) + if err != nil { + a.Logger.Errorln("InstallDeno(): Failed to unzip downloaded zip file:", err) + return + } - err = copyFile(path.Join(tmpDir, "deno"), a.DenoBin) - if err != nil { - a.Logger.Errorln("GetDeno(): Failed to copy deno file to install dir:", err) - return + err = copyFile(path.Join(tmpDir, "deno"), a.DenoBin) + if err != nil { + a.Logger.Errorln("InstallDeno(): Failed to copy deno file to install dir:", err) + return + } } err = os.Chmod(a.DenoBin, 0755) if err != nil { - a.Logger.Errorln("GetDeno(): Failed to chmod deno binary:", err) + a.Logger.Errorln("InstallDeno(): Failed to chmod deno binary:", err) return } } +// GetAgentCheckInConfig will get the agent configuration from the server. +// The Windows agent stores the configuration in the registry. The UNIX agent does not store the config. +// @return AgentCheckInConfig +func (a *Agent) GetAgentCheckInConfig(ret AgentCheckInConfig) AgentCheckInConfig { + // TODO: Persist the config to disk. + return ret +} + +// windows only below TODO add into stub file +func (a *Agent) PlatVer() (string, error) { return "", nil } + +func (a *Agent) SendSoftware() {} + +func (a *Agent) UninstallCleanup() {} + +func (a *Agent) RunMigrations() {} + +func GetServiceStatus(name string) (string, error) { return "", nil } + +func (a *Agent) GetPython(force bool) {} + type SchedTask struct{ Name string } func (a *Agent) PatchMgmnt(enable bool) error { return nil } diff --git a/agent/agent_windows.go b/agent/agent_windows.go index c61a29a..cc22cbf 100644 --- a/agent/agent_windows.go +++ b/agent/agent_windows.go @@ -92,7 +92,7 @@ func NewAgentConfig() *rmm.AgentConfig { } } -func (a *Agent) RunScript(code string, shell string, args []string, timeout int, runasuser bool, envVars []string) (stdout, stderr string, exitcode int, e error) { +func (a *Agent) RunScript(code string, shell string, args []string, timeout int, runasuser bool, envVars []string, nushellEnableConfig bool, denoDefaultPermissions string) (stdout, stderr string, exitcode int, e error) { content := []byte(code) @@ -158,7 +158,18 @@ func (a *Agent) RunScript(code string, shell string, args []string, timeout int, exe = tmpfn.Name() case "nushell": exe = a.NuBin - cmdArgs = []string{"--no-config-file", tmpfn.Name()} + var nushellArgs []string + if nushellEnableConfig { + nushellArgs = []string{ + "--config", + path.Join(a.ProgramDir, "etc", "nushell", "config.nu"), + "--env-config", + path.Join(a.ProgramDir, "etc", "nushell", "env.nu"), + } + } else { + nushellArgs = []string{"--no-config-file"} + } + cmdArgs = append(nushellArgs, tmpfn.Name()) if !trmm.FileExists(a.NuBin) { a.Logger.Errorln("RunScript(): Executable does not exist. Install Nu and try again:", a.NuBin) err := errors.New("File Not Found: " + a.NuBin) @@ -177,6 +188,8 @@ func (a *Agent) RunScript(code string, shell string, args []string, timeout int, // https://docs.deno.com/runtime/manual/basics/permissions#permissions-list // DENO_PERMISSIONS is not an official environment variable. // https://docs.deno.com/runtime/manual/basics/env_variables + // DENO_DEFAULT_PERMISSIONS is used if not found in the environment variables. + found := false for i, v := range envVars { if strings.HasPrefix(v, "DENO_PERMISSIONS=") { permissions := strings.Split(v, "=")[1] @@ -184,12 +197,13 @@ func (a *Agent) RunScript(code string, shell string, args []string, timeout int, // Remove the DENO_PERMISSIONS variable from the environment variables slice. // It's possible more variables may exist with the same prefix. envVars = append(envVars[:i], envVars[i+1:]...) + found = true break } } - - // Can't append a variadic slice after a string arg. - // https://pkg.go.dev/builtin#append + if !found && denoDefaultPermissions != "" { + cmdArgs = append(cmdArgs, strings.Split(denoDefaultPermissions, " ")...) + } cmdArgs = append(cmdArgs, tmpfn.Name()) } @@ -879,14 +893,20 @@ func (a *Agent) GetPython(force bool) { } } -// GetNushell will download nushell from GitHub and install (copy) it to ProgramDir\bin, where ProgramDir is +// InstallNushell will download nushell from GitHub and install (copy) it to ProgramDir\bin, where ProgramDir is // initialized to C:\Program Files\TacticalAgent -func (a *Agent) GetNushell(force bool) { +func (a *Agent) InstallNushell(force bool) { + conf := a.GetAgentCheckInConfig(a.GetCheckInConfFromAPI()) + if !conf.InstallNushell { + return + } + if trmm.FileExists(a.NuBin) { if force { + a.Logger.Debugln(a.NuBin, "InstallNushell(): Forced install. Removing nu.exe binary.") err := os.Remove(a.NuBin) if err != nil { - a.Logger.Errorln("GetNushell(): Error removing nu binary:", err) + a.Logger.Errorln("InstallNushell(): Error removing nu.exe binary:", err) return } } else { @@ -898,50 +918,96 @@ func (a *Agent) GetNushell(force bool) { if !trmm.FileExists(programBinDir) { err := os.MkdirAll(programBinDir, 0755) if err != nil { - a.Logger.Errorln("GetNushell(): Error creating Program Files bin folder:", err) + a.Logger.Errorln("InstallNushell(): Error creating Program Files bin folder:", err) return } } + if conf.NushellEnableConfig { + // Create 0-byte config files for Nushell + nushellPath := path.Join(a.ProgramDir, "etc", "nushell") + nushellConfig := path.Join(nushellPath, "config.nu") + nushellEnv := path.Join(nushellPath, "env.nu") + if !trmm.FileExists(nushellPath) { + err := os.MkdirAll(nushellPath, 0755) + if err != nil { + a.Logger.Errorln("InstallNushell(): Error creating Program Files/nushell:", err) + return + } + } + + if !trmm.FileExists(nushellConfig) { + _, err := os.Create(nushellConfig) + if err != nil { + a.Logger.Errorln("InstallNushell(): Error creating nushell config.nu:", err) + return + } + err = os.Chmod(nushellConfig, 0744) + if err != nil { + a.Logger.Errorln("InstallNushell(): Error changing permissions for nushell config.nu:", err) + return + } + } + if !trmm.FileExists(nushellEnv) { + _, err := os.Create(nushellEnv) + if err != nil { + a.Logger.Errorln("InstallNushell(): Error creating nushell env.nu:", err) + return + } + err = os.Chmod(nushellEnv, 0744) + if err != nil { + a.Logger.Errorln("InstallNushell(): Error changing permissions for nushell env.nu:", err) + return + } + } + } + var ( assetName string url string ) - switch runtime.GOOS { - case "windows": - switch runtime.GOARCH { - case "amd64": - // https://github.com/nushell/nushell/releases/download/0.87.0/nu-0.87.0-x86_64-windows-msvc-full.zip - assetName = fmt.Sprintf("nu-%s-x86_64-windows-msvc-full.zip", nuVersion) - case "arm64": - // https://github.com/nushell/nushell/releases/download/0.87.0/nu-0.87.0-aarch64-windows-msvc-full.zip - assetName = fmt.Sprintf("nu-%s-aarch64-windows-msvc-full.zip", nuVersion) + if conf.InstallNushellUrl != "" { + url = conf.InstallNushellUrl + url = strings.Replace(url, "{OS}", runtime.GOOS, -1) + url = strings.Replace(url, "{ARCH}", runtime.GOARCH, -1) + url = strings.Replace(url, "{VERSION}", conf.InstallNushellVersion, -1) + } else { + switch runtime.GOOS { + case "windows": + switch runtime.GOARCH { + case "amd64": + // https://github.com/nushell/nushell/releases/download/0.87.0/nu-0.87.0-x86_64-windows-msvc-full.zip + assetName = fmt.Sprintf("nu-%s-x86_64-windows-msvc-full.zip", conf.InstallNushellVersion) + case "arm64": + // https://github.com/nushell/nushell/releases/download/0.87.0/nu-0.87.0-aarch64-windows-msvc-full.zip + assetName = fmt.Sprintf("nu-%s-aarch64-windows-msvc-full.zip", conf.InstallNushellVersion) + default: + a.Logger.Debugln("InstallNushell(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS) + return + } default: - a.Logger.Debugln("GetNushell(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS) + a.Logger.Debugln("InstallNushell(): Unsupported OS:", runtime.GOOS) return } - default: - a.Logger.Debugln("GetNushell(): Unsupported OS:", runtime.GOOS) - return + url = fmt.Sprintf("https://github.com/nushell/nushell/releases/download/%s/%s", conf.InstallNushellVersion, assetName) } - url = fmt.Sprintf("https://github.com/nushell/nushell/releases/download/%s/%s", nuVersion, assetName) - a.Logger.Debugln("GetNushell(): Nu download url:", url) + a.Logger.Debugln("InstallNushell(): Nu download url:", url) tmpDir, err := os.MkdirTemp("", "trmm") if err != nil { - a.Logger.Errorln("GetNushell(): Error creating temp directory:", err) + a.Logger.Errorln("InstallNushell(): Error creating temp directory:", err) return } defer func(path string) { err := os.RemoveAll(path) if err != nil { - a.Logger.Errorln("GetNushell(): Error removing temp directory:", err) + a.Logger.Errorln("InstallNushell(): Error removing temp directory:", err) } }(tmpDir) tmpAssetName := filepath.Join(tmpDir, assetName) - a.Logger.Debugln("GetNushell(): tmpAssetName:", tmpAssetName) + a.Logger.Debugln("InstallNushell(): tmpAssetName:", tmpAssetName) rClient := resty.New() rClient.SetTimeout(20 * time.Minute) @@ -954,35 +1020,50 @@ func (a *Agent) GetNushell(force bool) { r, err := rClient.R().SetOutput(tmpAssetName).Get(url) if err != nil { - a.Logger.Errorln("GetNushell(): Unable to download nu from github.", err) + a.Logger.Errorln("InstallNushell(): Unable to download nu:", err) return } if r.IsError() { - a.Logger.Errorln("GetNushell(): Unable to download nu from github. Status code", r.StatusCode()) + a.Logger.Errorln("InstallNushell(): Unable to download nu. Status code:", r.StatusCode()) return } - err = Unzip(tmpAssetName, tmpDir) - if err != nil { - a.Logger.Errorln("GetNushell(): Failed to unzip downloaded zip file:", err) - return - } + if conf.InstallNushellUrl != "" { + // InstallNushellUrl is not compressed. + err = copyFile(path.Join(tmpDir, tmpAssetName), a.NuBin) + if err != nil { + a.Logger.Errorln("InstallNushell(): Failed to copy nu file to install dir:", err) + return + } + } else { + err = Unzip(tmpAssetName, tmpDir) + if err != nil { + a.Logger.Errorln("InstallNushell(): Failed to unzip downloaded zip file:", err) + return + } - err = copyFile(path.Join(tmpDir, "nu.exe"), a.NuBin) - if err != nil { - a.Logger.Errorln("GetNushell(): Failed to copy nu.exe file to install dir:", err) - return + err = copyFile(path.Join(tmpDir, "nu.exe"), a.NuBin) + if err != nil { + a.Logger.Errorln("InstallNushell(): Failed to copy nu.exe file to install dir:", err) + return + } } } -// GetDeno will download deno from GitHub and install (copy) it to nixAgentBinDir -func (a *Agent) GetDeno(force bool) { +// InstallDeno will download deno from GitHub and install (copy) it to ProgramDir\bin, where ProgramDir is +// initialized to C:\Program Files\TacticalAgent +func (a *Agent) InstallDeno(force bool) { + conf := a.GetAgentCheckInConfig(a.GetCheckInConfFromAPI()) + if !conf.InstallDeno { + return + } + if trmm.FileExists(a.DenoBin) { if force { err := os.Remove(a.DenoBin) if err != nil { - a.Logger.Errorln("GetDeno(): Error removing deno binary:", err) + a.Logger.Errorln("InstallDeno(): Error removing deno binary:", err) return } } else { @@ -994,7 +1075,7 @@ func (a *Agent) GetDeno(force bool) { if !trmm.FileExists(programBinDir) { err := os.MkdirAll(programBinDir, 0755) if err != nil { - a.Logger.Errorln("GetDeno(): Error creating Program Files bin folder:", err) + a.Logger.Errorln("InstallDeno(): Error creating Program Files bin folder:", err) return } } @@ -1004,45 +1085,44 @@ func (a *Agent) GetDeno(force bool) { url string ) - switch runtime.GOOS { - case "windows": - switch runtime.GOARCH { - case "amd64": - // https://github.com/denoland/deno/releases/download/v1.38.2/deno-x86_64-pc-windows-msvc.zip - assetName = fmt.Sprintf("deno-x86_64-pc-windows-msvc.zip") + if conf.InstallDenoUrl != "" { + url = conf.InstallDenoUrl + url = strings.Replace(url, "{OS}", runtime.GOOS, -1) + url = strings.Replace(url, "{ARCH}", runtime.GOARCH, -1) + url = strings.Replace(url, "{VERSION}", conf.InstallDenoVersion, -1) + } else { + switch runtime.GOOS { + case "windows": + switch runtime.GOARCH { + case "amd64": + // https://github.com/denoland/deno/releases/download/v1.38.2/deno-x86_64-pc-windows-msvc.zip + assetName = fmt.Sprintf("deno-x86_64-pc-windows-msvc.zip") + default: + a.Logger.Debugln("InstallDeno(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS) + return + } default: - a.Logger.Debugln("GetDeno(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS) + a.Logger.Debugln("InstallDeno(): Unsupported OS:", runtime.GOOS) return } - default: - a.Logger.Debugln("GetDeno(): Unsupported OS:", runtime.GOOS) - return + url = fmt.Sprintf("https://github.com/denoland/deno/releases/download/%s/%s", conf.InstallDenoVersion, assetName) } - url = fmt.Sprintf("https://github.com/denoland/deno/releases/download/%s/%s", denoVersion, assetName) - a.Logger.Debugln("GetDeno(): Deno download url:", url) + a.Logger.Debugln("InstallDeno(): Deno download url:", url) tmpDir, err := os.MkdirTemp("", "trmm") if err != nil { - a.Logger.Errorln("GetDeno(): Error creating temp directory:", err) + a.Logger.Errorln("InstallDeno(): Error creating temp directory:", err) return } defer func(path string) { err := os.RemoveAll(path) if err != nil { - a.Logger.Errorln("GetDeno(): Error removing temp directory:", err) + a.Logger.Errorln("InstallDeno(): Error removing temp directory:", err) } }(tmpDir) tmpAssetName := filepath.Join(tmpDir, assetName) - a.Logger.Debugln("GetDeno(): tmpAssetName:", tmpAssetName) - - if !trmm.FileExists(nixAgentBinDir) { - err = os.MkdirAll(nixAgentBinDir, 0755) - if err != nil { - a.Logger.Errorln("GetDeno(): Error creating nixAgentBinDir:", err) - return - } - } + a.Logger.Debugln("InstallDeno(): tmpAssetName:", tmpAssetName) rClient := resty.New() rClient.SetTimeout(20 * time.Minute) @@ -1055,24 +1135,34 @@ func (a *Agent) GetDeno(force bool) { r, err := rClient.R().SetOutput(tmpAssetName).Get(url) if err != nil { - a.Logger.Errorln("GetDeno(): Unable to download deno from github.", err) + a.Logger.Errorln("InstallDeno(): Unable to download deno:", err) return } if r.IsError() { - a.Logger.Errorln("GetDeno(): Unable to download deno from github. Status code", r.StatusCode()) + a.Logger.Errorln("InstallDeno(): Unable to download deno. Status code:", r.StatusCode()) return } - err = Unzip(tmpAssetName, tmpDir) - if err != nil { - a.Logger.Errorln("GetDeno(): Failed to unzip downloaded zip file:", err) - return - } + if conf.InstallDenoUrl != "" { + // InstallDenoUrl is not compressed. + err = copyFile(path.Join(tmpDir, tmpAssetName), a.DenoBin) + if err != nil { + a.Logger.Errorln("InstallDeno(): Failed to copy deno file to install dir:", err) + return + } + } else { + // GitHub asset is zip compressed. + err = Unzip(tmpAssetName, tmpDir) + if err != nil { + a.Logger.Errorln("InstallDeno(): Failed to unzip downloaded zip file:", err) + return + } - err = copyFile(path.Join(tmpDir, "deno.exe"), a.DenoBin) - if err != nil { - a.Logger.Errorln("GetDeno(): Failed to copy deno.exe file to install dir:", err) - return + err = copyFile(path.Join(tmpDir, "deno.exe"), a.DenoBin) + if err != nil { + a.Logger.Errorln("InstallDeno(): Failed to copy deno.exe file to install dir:", err) + return + } } } diff --git a/agent/checks.go b/agent/checks.go index 930e947..5e0905d 100644 --- a/agent/checks.go +++ b/agent/checks.go @@ -169,7 +169,7 @@ type ScriptCheckResult struct { // ScriptCheck runs either bat, powershell or python script func (a *Agent) ScriptCheck(data rmm.Check, r *resty.Client) { start := time.Now() - stdout, stderr, retcode, _ := a.RunScript(data.Script.Code, data.Script.Shell, data.ScriptArgs, data.Timeout, data.Script.RunAsUser, data.EnvVars) + stdout, stderr, retcode, _ := a.RunScript(data.Script.Code, data.Script.Shell, data.ScriptArgs, data.Timeout, data.Script.RunAsUser, data.EnvVars, data.NushellEnableConfig, data.DenoDefaultPermissions) payload := ScriptCheckResult{ ID: data.CheckPK, diff --git a/agent/choco_windows.go b/agent/choco_windows.go index f75b15f..7019785 100644 --- a/agent/choco_windows.go +++ b/agent/choco_windows.go @@ -45,7 +45,7 @@ func (a *Agent) InstallChoco() { return } - _, _, exitcode, err := a.RunScript(string(r.Body()), "powershell", []string{}, 900, false, []string{}) + _, _, exitcode, err := a.RunScript(string(r.Body()), "powershell", []string{}, 900, false, []string{}, false, "") if err != nil { a.Logger.Debugln(err) a.rClient.R().SetBody(result).Post(url) diff --git a/agent/embed_darwin.go b/agent/embed_darwin.go index 6a42a6d..c69225a 100644 --- a/agent/embed_darwin.go +++ b/agent/embed_darwin.go @@ -20,5 +20,5 @@ import _ "embed" var ventura_mesh_fix string func (a *Agent) FixVenturaMesh() { - a.RunScript(ventura_mesh_fix, "foo", []string{}, 45, false, []string{}) + a.RunScript(ventura_mesh_fix, "foo", []string{}, 45, false, []string{}, false, "") } diff --git a/agent/rpc.go b/agent/rpc.go index 03b1508..a58e6f5 100644 --- a/agent/rpc.go +++ b/agent/rpc.go @@ -26,22 +26,24 @@ import ( ) type NatsMsg struct { - Func string `json:"func"` - Timeout int `json:"timeout"` - Data map[string]string `json:"payload"` - ScriptArgs []string `json:"script_args"` - ProcPID int32 `json:"procpid"` - TaskPK int `json:"taskpk"` - ScheduledTask SchedTask `json:"schedtaskpayload"` - RecoveryCommand string `json:"recoverycommand"` - UpdateGUIDs []string `json:"guids"` - ChocoProgName string `json:"choco_prog_name"` - PendingActionPK int `json:"pending_action_pk"` - PatchMgmt bool `json:"patch_mgmt"` - ID int `json:"id"` - Code string `json:"code"` - RunAsUser bool `json:"run_as_user"` - EnvVars []string `json:"env_vars"` + Func string `json:"func"` + Timeout int `json:"timeout"` + Data map[string]string `json:"payload"` + ScriptArgs []string `json:"script_args"` + ProcPID int32 `json:"procpid"` + TaskPK int `json:"taskpk"` + ScheduledTask SchedTask `json:"schedtaskpayload"` + RecoveryCommand string `json:"recoverycommand"` + UpdateGUIDs []string `json:"guids"` + ChocoProgName string `json:"choco_prog_name"` + PendingActionPK int `json:"pending_action_pk"` + PatchMgmt bool `json:"patch_mgmt"` + ID int `json:"id"` + Code string `json:"code"` + RunAsUser bool `json:"run_as_user"` + EnvVars []string `json:"env_vars"` + NushellEnableConfig bool `json:"nushell_enable_config"` + DenoDefaultPermissions string `json:"deno_default_permissions"` } var ( @@ -264,7 +266,7 @@ func (a *Agent) RunRPC() { var resultData rmm.RunScriptResp ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) start := time.Now() - stdout, stderr, retcode, err := a.RunScript(p.Data["code"], p.Data["shell"], p.ScriptArgs, p.Timeout, p.RunAsUser, p.EnvVars) + stdout, stderr, retcode, err := a.RunScript(p.Data["code"], p.Data["shell"], p.ScriptArgs, p.Timeout, p.RunAsUser, p.EnvVars, p.NushellEnableConfig, p.DenoDefaultPermissions) resultData.ExecTime = time.Since(start).Seconds() resultData.ID = p.ID @@ -294,7 +296,7 @@ func (a *Agent) RunRPC() { var retData rmm.RunScriptResp ret := codec.NewEncoderBytes(&resp, new(codec.MsgpackHandle)) start := time.Now() - stdout, stderr, retcode, err := a.RunScript(p.Data["code"], p.Data["shell"], p.ScriptArgs, p.Timeout, p.RunAsUser, p.EnvVars) + stdout, stderr, retcode, err := a.RunScript(p.Data["code"], p.Data["shell"], p.ScriptArgs, p.Timeout, p.RunAsUser, p.EnvVars, p.NushellEnableConfig, p.DenoDefaultPermissions) retData.ExecTime = time.Since(start).Seconds() if err != nil { @@ -438,9 +440,9 @@ func (a *Agent) RunRPC() { case "installpython": go a.GetPython(true) case "installnushell": - go a.GetNushell(true) + go a.InstallNushell(true) case "installdeno": - go a.GetDeno(true) + go a.InstallDeno(true) case "installchoco": go a.InstallChoco() case "installwithchoco": diff --git a/agent/svc.go b/agent/svc.go index b04bb9d..0827448 100644 --- a/agent/svc.go +++ b/agent/svc.go @@ -29,15 +29,23 @@ func (a *Agent) RunAsService(nc *nats.Conn) { } type AgentCheckInConfig struct { - Hello int `json:"checkin_hello"` - AgentInfo int `json:"checkin_agentinfo"` - WinSvc int `json:"checkin_winsvc"` - PubIP int `json:"checkin_pubip"` - Disks int `json:"checkin_disks"` - SW int `json:"checkin_sw"` - WMI int `json:"checkin_wmi"` - SyncMesh int `json:"checkin_syncmesh"` - LimitData bool `json:"limit_data"` + Hello int `json:"checkin_hello"` + AgentInfo int `json:"checkin_agentinfo"` + WinSvc int `json:"checkin_winsvc"` + PubIP int `json:"checkin_pubip"` + Disks int `json:"checkin_disks"` + SW int `json:"checkin_sw"` + WMI int `json:"checkin_wmi"` + SyncMesh int `json:"checkin_syncmesh"` + LimitData bool `json:"limit_data"` + InstallNushell bool `json:"install_nushell"` + InstallNushellVersion string `json:"install_nushell_version"` + InstallNushellUrl string `json:"install_nushell_url"` + NushellEnableConfig bool `json:"nushell_enable_config"` + InstallDeno bool `json:"install_deno"` + InstallDenoVersion string `json:"install_deno_version"` + InstallDenoUrl string `json:"install_deno_url"` + DenoDefaultPermissions string `json:"deno_default_permissions"` } func (a *Agent) AgentSvc(nc *nats.Conn) { @@ -49,8 +57,6 @@ func (a *Agent) AgentSvc(nc *nats.Conn) { a.Logger.Errorln("AgentSvc() createWinTempDir():", err) } } - a.GetNushell(false) - a.GetDeno(false) a.RunMigrations() @@ -64,8 +70,9 @@ func (a *Agent) AgentSvc(nc *nats.Conn) { a.CleanupAgentUpdates() } + // Windows has GetAgentCheckInConfig() while unix has a stub GetAgentCheckInConfig() conf := a.GetAgentCheckInConfig(a.GetCheckInConfFromAPI()) - a.Logger.Debugf("+%v\n", conf) + a.Logger.Debugf("AgentCheckInConf: %+v\n", conf) for _, s := range natsCheckin { if conf.LimitData && stringInSlice(s, limitNatsData) { continue @@ -75,6 +82,15 @@ func (a *Agent) AgentSvc(nc *nats.Conn) { } } + // The server conf check is also done in the functions to keep the parameters the same. + // Don't force a download when restarting the service. + if conf.InstallNushell { + a.InstallNushell(false) + } + if conf.InstallDeno { + a.InstallDeno(false) + } + go a.SyncMeshNodeID() time.Sleep(time.Duration(randRange(1, 3)) * time.Second) @@ -142,6 +158,14 @@ func (a *Agent) GetCheckInConfFromAPI() AgentCheckInConfig { ret.WMI = randRange(3000, 4000) ret.SyncMesh = randRange(800, 1200) ret.LimitData = false + ret.InstallNushell = false + ret.InstallNushellVersion = "" + ret.InstallNushellUrl = "" + ret.NushellEnableConfig = false + ret.InstallDeno = false + ret.InstallDenoVersion = "" + ret.InstallDenoUrl = "" + ret.DenoDefaultPermissions = "" } else { ret.Hello = r.Result().(*AgentCheckInConfig).Hello ret.AgentInfo = r.Result().(*AgentCheckInConfig).AgentInfo @@ -152,6 +176,14 @@ func (a *Agent) GetCheckInConfFromAPI() AgentCheckInConfig { ret.WMI = r.Result().(*AgentCheckInConfig).WMI ret.SyncMesh = r.Result().(*AgentCheckInConfig).SyncMesh ret.LimitData = r.Result().(*AgentCheckInConfig).LimitData + ret.InstallNushell = r.Result().(*AgentCheckInConfig).InstallNushell + ret.InstallNushellVersion = r.Result().(*AgentCheckInConfig).InstallNushellVersion + ret.InstallNushellUrl = r.Result().(*AgentCheckInConfig).InstallNushellUrl + ret.NushellEnableConfig = r.Result().(*AgentCheckInConfig).NushellEnableConfig + ret.InstallDeno = r.Result().(*AgentCheckInConfig).InstallDeno + ret.InstallDenoVersion = r.Result().(*AgentCheckInConfig).InstallDenoVersion + ret.InstallDenoUrl = r.Result().(*AgentCheckInConfig).InstallDenoUrl + ret.DenoDefaultPermissions = r.Result().(*AgentCheckInConfig).DenoDefaultPermissions } return ret } diff --git a/agent/tasks_windows.go b/agent/tasks_windows.go index ab199f4..a4db248 100644 --- a/agent/tasks_windows.go +++ b/agent/tasks_windows.go @@ -14,13 +14,13 @@ package agent import ( "encoding/json" "fmt" + "github.com/amidaware/taskmaster" "os" "path/filepath" "strings" "time" rmm "github.com/amidaware/rmmagent/shared" - "github.com/amidaware/taskmaster" "github.com/rickb777/date/period" ) @@ -59,7 +59,7 @@ func (a *Agent) RunTask(id int) error { action_start := time.Now() if action.ActionType == "script" { - stdout, stderr, retcode, err := a.RunScript(action.Code, action.Shell, action.Args, action.Timeout, action.RunAsUser, action.EnvVars) + stdout, stderr, retcode, err := a.RunScript(action.Code, action.Shell, action.Args, action.Timeout, action.RunAsUser, action.EnvVars, action.NushellEnableConfig, action.DenoDefaultPermissions) if err != nil { a.Logger.Debugln(err) diff --git a/main.go b/main.go index 0d46a62..4ccbdbf 100644 --- a/main.go +++ b/main.go @@ -117,10 +117,10 @@ func main() { fmt.Println(a.PublicIP()) case "getpython": a.GetPython(true) - case "getdeno": - a.GetDeno(true) - case "getnushell": - a.GetNushell(true) + case "installdeno": + a.InstallDeno(true) + case "installnushell": + a.InstallNushell(true) case "runmigrations": a.RunMigrations() case "recovermesh": diff --git a/shared/types.go b/shared/types.go index d7a6f24..f98beb9 100644 --- a/shared/types.go +++ b/shared/types.go @@ -157,29 +157,31 @@ type CheckInfo struct { } type Check struct { - Script Script `json:"script"` - AssignedTasks []AssignedTask `json:"assigned_tasks"` - CheckPK int `json:"id"` - CheckType string `json:"check_type"` - Status string `json:"status"` - Threshold int `json:"threshold"` - Disk string `json:"disk"` - IP string `json:"ip"` - ScriptArgs []string `json:"script_args"` - EnvVars []string `json:"env_vars"` - Timeout int `json:"timeout"` - ServiceName string `json:"svc_name"` - PassStartPending bool `json:"pass_if_start_pending"` - PassNotExist bool `json:"pass_if_svc_not_exist"` - RestartIfStopped bool `json:"restart_if_stopped"` - LogName string `json:"log_name"` - EventID int `json:"event_id"` - EventIDWildcard bool `json:"event_id_is_wildcard"` - EventType string `json:"event_type"` - EventSource string `json:"event_source"` - EventMessage string `json:"event_message"` - FailWhen string `json:"fail_when"` - SearchLastDays int `json:"search_last_days"` + Script Script `json:"script"` + AssignedTasks []AssignedTask `json:"assigned_tasks"` + CheckPK int `json:"id"` + CheckType string `json:"check_type"` + Status string `json:"status"` + Threshold int `json:"threshold"` + Disk string `json:"disk"` + IP string `json:"ip"` + ScriptArgs []string `json:"script_args"` + EnvVars []string `json:"env_vars"` + NushellEnableConfig bool `json:"nushell_enable_config"` + DenoDefaultPermissions string `json:"deno_default_permissions"` + Timeout int `json:"timeout"` + ServiceName string `json:"svc_name"` + PassStartPending bool `json:"pass_if_start_pending"` + PassNotExist bool `json:"pass_if_svc_not_exist"` + RestartIfStopped bool `json:"restart_if_stopped"` + LogName string `json:"log_name"` + EventID int `json:"event_id"` + EventIDWildcard bool `json:"event_id_is_wildcard"` + EventType string `json:"event_type"` + EventSource string `json:"event_source"` + EventMessage string `json:"event_message"` + FailWhen string `json:"fail_when"` + SearchLastDays int `json:"search_last_days"` } type AllChecks struct { @@ -188,15 +190,17 @@ type AllChecks struct { } type TaskAction struct { - ActionType string `json:"type"` - Command string `json:"command"` - Shell string `json:"shell"` - ScriptName string `json:"script_name"` - Code string `json:"code"` - Args []string `json:"script_args"` - Timeout int `json:"timeout"` - RunAsUser bool `json:"run_as_user"` - EnvVars []string `json:"env_vars"` + ActionType string `json:"type"` + Command string `json:"command"` + Shell string `json:"shell"` + ScriptName string `json:"script_name"` + Code string `json:"code"` + Args []string `json:"script_args"` + Timeout int `json:"timeout"` + RunAsUser bool `json:"run_as_user"` + EnvVars []string `json:"env_vars"` + NushellEnableConfig bool `json:"nushell_enable_config"` + DenoDefaultPermissions string `json:"deno_default_permissions"` } type AutomatedTask struct {