diff --git a/.vscode/settings.json b/.vscode/settings.json index f094099..db81063 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -17,5 +17,10 @@ "usePlaceholders": true, "completeUnimported": true, "staticcheck": true, - } + }, + "go.testFlags": [ + "-v", + "-vet=off", + "-timeout=300s" + ] } \ No newline at end of file diff --git a/README.md b/README.md index 4a236a8..282b4de 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,11 @@ go generate $env:CGO_ENABLED="0";$env:GOOS="windows";$env:GOARCH="amd64"; go build -ldflags "-s -w -X 'main.version=v2.0.4'" -o build/output/tacticalrmm.exe ``` +#### building the agent - freebsd +``` +env GOOS=freebsd GOARCH=amd64 go build -ldflags "-s -w -X 'main.version=v2.0.4'" -o build/output/rmmagent +``` + ### tests Navigate to repo directory ``` diff --git a/agent/agent.go b/agent/agent.go index 0b7d041..5980788 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -27,11 +27,11 @@ import ( "time" rmm "github.com/amidaware/rmmagent/shared" - ps "github.com/elastic/go-sysinfo" gocmd "github.com/go-cmd/cmd" "github.com/go-resty/resty/v2" "github.com/kardianos/service" nats "github.com/nats-io/nats.go" + ps "github.com/redanthrax/go-sysinfo" "github.com/shirou/gopsutil/v3/cpu" "github.com/sirupsen/logrus" trmm "github.com/wh1te909/trmm-shared" @@ -115,7 +115,7 @@ func New(logger *logrus.Logger, version string) *Agent { } if ac.NatsProxyPort == "" { - natsProxyPort = "443" + natsProxyPort = "8000" } // check if using nats standard tcp, otherwise use nats websockets by default diff --git a/agent/agent_test.go b/agent/agent_test.go index 8802576..c790147 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -7,7 +7,7 @@ import ( ) var ( - version = "2.0.4" + version = "2.1.0-dev" lg = logrus.New() a = New(lg, version) ) diff --git a/agent/checkin.go b/agent/checkin.go index 9990198..6e99372 100644 --- a/agent/checkin.go +++ b/agent/checkin.go @@ -72,6 +72,7 @@ func (a *Agent) NatsMessage(nc *nats.Conn, mode string) { a.Logger.Debugln(mode, payload) ret.Encode(payload) + a.Logger.Infoln(a.AgentID, mode, payload) nc.PublishRequest(a.AgentID, mode, resp) } diff --git a/agent/install_freebsd.go b/agent/install_freebsd.go new file mode 100644 index 0000000..a4117fe --- /dev/null +++ b/agent/install_freebsd.go @@ -0,0 +1,216 @@ +//go:build freebsd +// +build freebsd + +/* +Copyright 2022 AmidaWare LLC. + +Licensed under the Tactical RMM License Version 1.0 (the “License”). +You may only use the Licensed Software in accordance with the License. +A copy of the License is available at: + +https://license.tacticalrmm.com + +*/ + +package agent + +import ( + "fmt" + "log" + "net/url" + "regexp" + "strconv" + "strings" + "time" + + "github.com/go-resty/resty/v2" + "github.com/spf13/viper" + trmm "github.com/wh1te909/trmm-shared" +) + +const ( + etcConfig = "/etc/tacticalagent" +) + +func (a *Agent) Install(i *Installer) { + a.checkExistingAndRemove(i.Silent) + + i.Headers = map[string]string{ + "content-type": "application/json", + "Authorization": fmt.Sprintf("Token %s", i.Token), + } + a.AgentID = GenerateAgentID() + a.Logger.Debugln("Agent ID:", a.AgentID) + + u, err := url.Parse(i.RMM) + if err != nil { + a.installerMsg(err.Error(), "error", i.Silent) + } + + if u.Scheme != "https" && u.Scheme != "http" { + a.installerMsg("Invalid URL (must contain https or http)", "error", i.Silent) + } + + // will match either ipv4 , or ipv4:port + var ipPort = regexp.MustCompile(`[0-9]+(?:\.[0-9]+){3}(:[0-9]+)?`) + + // if ipv4:port, strip the port to get ip for salt master + if ipPort.MatchString(u.Host) && strings.Contains(u.Host, ":") { + i.SaltMaster = strings.Split(u.Host, ":")[0] + } else if strings.Contains(u.Host, ":") { + i.SaltMaster = strings.Split(u.Host, ":")[0] + } else { + i.SaltMaster = u.Host + } + + a.Logger.Debugln("API:", i.SaltMaster) + + terr := TestTCP(fmt.Sprintf("%s:4222", i.SaltMaster)) + if terr != nil { + a.installerMsg(fmt.Sprintf("ERROR: Either port 4222 TCP is not open on your RMM, or nats.service is not running.\n\n%s", terr.Error()), "error", i.Silent) + } + + baseURL := u.Scheme + "://" + u.Host + a.Logger.Debugln("Base URL:", baseURL) + + iClient := resty.New() + iClient.SetCloseConnection(true) + iClient.SetTimeout(15 * time.Second) + iClient.SetDebug(a.Debug) + iClient.SetHeaders(i.Headers) + + // set proxy if applicable + if len(i.Proxy) > 0 { + a.Logger.Infoln("Using proxy:", i.Proxy) + iClient.SetProxy(i.Proxy) + } + + creds, cerr := iClient.R().Get(fmt.Sprintf("%s/api/v3/installer/", baseURL)) + if cerr != nil { + a.installerMsg(cerr.Error(), "error", i.Silent) + } + if creds.StatusCode() == 401 { + a.installerMsg("Installer token has expired. Please generate a new one.", "error", i.Silent) + } + + verPayload := map[string]string{"version": a.Version} + iVersion, ierr := iClient.R().SetBody(verPayload).Post(fmt.Sprintf("%s/api/v3/installer/", baseURL)) + if ierr != nil { + a.installerMsg(ierr.Error(), "error", i.Silent) + } + if iVersion.StatusCode() != 200 { + a.installerMsg(DjangoStringResp(iVersion.String()), "error", i.Silent) + } + + rClient := resty.New() + rClient.SetCloseConnection(true) + rClient.SetTimeout(i.Timeout * time.Second) + rClient.SetDebug(a.Debug) + // set rest knox headers + rClient.SetHeaders(i.Headers) + + // set local cert if applicable + if len(i.Cert) > 0 { + if !trmm.FileExists(i.Cert) { + a.installerMsg(fmt.Sprintf("%s does not exist", i.Cert), "error", i.Silent) + } + rClient.SetRootCertificate(i.Cert) + } + + if len(i.Proxy) > 0 { + rClient.SetProxy(i.Proxy) + } + + a.Logger.Infoln("Adding agent to dashboard") + // add agent + type NewAgentResp struct { + AgentPK int `json:"pk"` + Token string `json:"token"` + } + agentPayload := map[string]interface{}{ + "agent_id": a.AgentID, + "hostname": a.Hostname, + "site": i.SiteID, + "monitoring_type": i.AgentType, + "mesh_node_id": "", + "description": i.Description, + "goarch": a.GoArch, + "plat": a.Platform, + } + + r, err := rClient.R().SetBody(agentPayload).SetResult(&NewAgentResp{}).Post(fmt.Sprintf("%s/api/v3/newagent/", baseURL)) + if err != nil { + a.installerMsg(err.Error(), "error", i.Silent) + } + if r.StatusCode() != 200 { + a.installerMsg(r.String(), "error", i.Silent) + } + + agentPK := r.Result().(*NewAgentResp).AgentPK + agentToken := r.Result().(*NewAgentResp).Token + a.Logger.Debugln("Agent token:", agentToken) + a.Logger.Debugln("Agent PK:", agentPK) + createAgentConfig(baseURL, a.AgentID, i.SaltMaster, agentToken, strconv.Itoa(agentPK), i.Cert, i.Proxy, i.MeshDir) + time.Sleep(1 * time.Second) + // refresh our agent with new values + a = New(a.Logger, a.Version) + a.Logger.Debugf("%+v\n", a) + // set new headers, no longer knox auth...use agent auth + rClient.SetHeaders(a.Headers) + time.Sleep(3 * time.Second) + // check in once + a.DoNatsCheckIn() + // send software api + a.SendSoftware() + a.Logger.Debugln("Creating temp dir") + a.CreateTRMMTempDir() + a.Logger.Infoln("Installing service...") + err = a.InstallService() + if err != nil { + a.installerMsg(err.Error(), "error", i.Silent) + } + + time.Sleep(1 * time.Second) + a.Logger.Infoln("Starting service...") + out := a.ControlService(winSvcName, "start") + if !out.Success { + a.installerMsg(out.ErrorMsg, "error", i.Silent) + } + + a.installerMsg("Installation was successfull!\nAllow a few minutes for the agent to properly display in the RMM", "info", i.Silent) +} + +func (a *Agent) checkExistingAndRemove(silent bool) {} + +func (a *Agent) installerMsg(msg, alert string, silent bool) { + if alert == "error" { + a.Logger.Fatalln(msg) + } else { + a.Logger.Info(msg) + } +} + +func createAgentConfig(baseurl, agentid, apiurl, token, agentpk, cert, proxy, meshdir string) { + viper.SetConfigType("json") + viper.Set("baseurl", baseurl) + viper.Set("agentid", agentid) + viper.Set("apiurl", apiurl) + viper.Set("token", token) + viper.Set("agentpk", agentpk) + viper.Set("cert", cert) + viper.Set("proxy", proxy) + viper.Set("meshdir", meshdir) + viper.SetConfigPermissions(0660) + err := viper.SafeWriteConfigAs(etcConfig) + if err != nil { + log.Fatalln("createAgentConfig", err) + } +} + +func (a *Agent) addDefenderExlusions() {} + +func DisableSleepHibernate() {} + +func EnablePing() {} + +func EnableRDP() {} diff --git a/agent/install_test.go b/agent/install_test.go index de748e5..64eb67a 100644 --- a/agent/install_test.go +++ b/agent/install_test.go @@ -1,62 +1,62 @@ package agent -//import ( - //"github.com/sirupsen/logrus" - //"github.com/spf13/viper" - //"testing" -//) +import ( + "github.com/sirupsen/logrus" + "github.com/spf13/viper" + "testing" +) -//func TestInstall(t *testing.T) { - //testTable := []struct { - //name string - //expectedError error - //version string - //log logrus.Logger - //}{ - //{ - //name: "Install", - //expectedError: nil, - //version: "2.0.4", - //log: *logrus.New(), - //}, - //{ - //name: "Install Error", - //expectedError: nil, - //version: "bad ver", - //log: *logrus.New(), - //}, - //} +func TestInstall(t *testing.T) { + testTable := []struct { + name string + expectedError error + version string + log logrus.Logger + }{ + { + name: "Install", + expectedError: nil, + version: "2.1.0-dev", + log: *logrus.New(), + }, + { + name: "Install Error", + expectedError: nil, + version: "bad ver", + log: *logrus.New(), + }, + } - //for _, tt := range testTable { - //t.Run(tt.name, func(t *testing.T) { - //a := New(&tt.log, tt.version) + for _, tt := range testTable { + t.Run(tt.name, func(t *testing.T) { + a := New(&tt.log, tt.version) - //viper.SetConfigName("testargs.json") - //viper.SetConfigType("json") - //viper.AddConfigPath(".") - //viper.ReadInConfig() + viper.SetConfigName("testargs.json") + viper.SetConfigType("json") + viper.AddConfigPath(".") + viper.ReadInConfig() - //installer := Installer{ - //RMM: viper.GetString("api"), - //ClientID: viper.GetInt("clientid"), - //SiteID: viper.GetInt("siteid"), - //Description: viper.GetString("description"), - //AgentType: viper.GetString("agenttype"), - //Power: viper.GetBool("power"), - //RDP: viper.GetBool("rdp"), - //Ping: viper.GetBool("ping"), - //Token: viper.GetString("token"), - //LocalMesh: viper.GetString("localmesh"), - //Cert: viper.GetString("cert"), - //Proxy: viper.GetString("proxy"), - //Timeout: viper.GetDuration("timeout"), - //Silent: viper.GetBool("silent"), - //NoMesh: viper.GetBool("nomesh"), - //MeshDir: viper.GetString("meshdir"), - //MeshNodeID: viper.GetString("meshnodeid"), - //} + installer := Installer{ + RMM: viper.GetString("api"), + ClientID: viper.GetInt("clientid"), + SiteID: viper.GetInt("siteid"), + Description: viper.GetString("description"), + AgentType: viper.GetString("agenttype"), + Power: viper.GetBool("power"), + RDP: viper.GetBool("rdp"), + Ping: viper.GetBool("ping"), + Token: viper.GetString("token"), + LocalMesh: viper.GetString("localmesh"), + Cert: viper.GetString("cert"), + Proxy: viper.GetString("proxy"), + Timeout: viper.GetDuration("timeout"), + Silent: viper.GetBool("silent"), + NoMesh: viper.GetBool("nomesh"), + MeshDir: viper.GetString("meshdir"), + MeshNodeID: viper.GetString("meshnodeid"), + } - //a.Install(&installer) - //}) - //} -//} + a.Install(&installer) + }) + } +} diff --git a/agent/rpc_test.go b/agent/rpc_test.go index 2aae279..9ef2358 100644 --- a/agent/rpc_test.go +++ b/agent/rpc_test.go @@ -6,6 +6,7 @@ import ( //uncomment to test rpc, comment to add back before commit, this test will always timeout func TestRunRPC(t *testing.T) { - //a := New(lg, version) - //a.RunRPC() + a := New(lg, version) + t.Log(a.NatsServer) + a.RunRPC() } diff --git a/go.mod b/go.mod index fd8034c..ab4ffa5 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.18 require ( github.com/StackExchange/wmi v1.2.1 - github.com/elastic/go-sysinfo v1.8.0 github.com/go-ole/go-ole v1.2.6 github.com/go-ping/ping v1.1.0 github.com/go-resty/resty/v2 v2.7.0 @@ -12,6 +11,7 @@ require ( github.com/iamacarpet/go-win64api v0.0.0-20220531152116-0aa0ec10b240 github.com/nats-io/nats-server/v2 v2.8.4 // indirect github.com/nats-io/nats.go v1.16.0 + github.com/redanthrax/go-sysinfo v1.8.2 github.com/rickb777/date v1.19.1 github.com/shirou/gopsutil/v3 v3.22.5 github.com/sirupsen/logrus v1.8.1 @@ -19,7 +19,7 @@ require ( github.com/wh1te909/go-win64api v0.0.0-20210906074314-ab23795a6ae5 github.com/wh1te909/trmm-shared v0.0.0-20220227075846-f9f757361139 golang.org/x/net v0.0.0-20220531201128-c960675eff93 // indirect - golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a + golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b ) require ( @@ -28,6 +28,7 @@ require ( ) require ( + github.com/elastic/go-sysinfo v1.8.0 github.com/jaypipes/ghw v0.9.0 github.com/kardianos/service v1.2.1 github.com/spf13/viper v1.12.0 @@ -59,7 +60,7 @@ require ( github.com/rickb777/plural v1.4.1 // indirect github.com/rogpeppe/go-internal v1.8.1 // indirect github.com/scjalliance/comshim v0.0.0-20190308082608-cf06d2532c4e // indirect - github.com/shirou/gopsutil v3.21.11+incompatible // indirect + github.com/shirou/gopsutil v3.21.11+incompatible github.com/spf13/afero v1.8.2 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect @@ -67,12 +68,11 @@ require ( github.com/subosito/gotenv v1.4.0 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/numcpus v0.5.0 // indirect - github.com/yusufpapurcu/wmi v1.2.2 // indirect + github.com/yusufpapurcu/wmi v1.2.2 golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect - golang.org/x/tools v0.1.11 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/ini.v1 v1.66.6 // indirect gopkg.in/toast.v1 v1.0.0-20180812000517-0a84660828b2 // indirect diff --git a/go.sum b/go.sum index f084d93..20190b2 100644 --- a/go.sum +++ b/go.sum @@ -243,6 +243,8 @@ github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c/go.mod h1:Om github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/redanthrax/go-sysinfo v1.8.2 h1:NkYXoYUDs31orM+NeIZpZyom1R9UPWP9np4DexR7q54= +github.com/redanthrax/go-sysinfo v1.8.2/go.mod h1:F/lqubyzBmM83Xcob+6Cy2V1CLY7pBv8FWzThqmView= github.com/rickb777/date v1.14.2/go.mod h1:swmf05C+hN+m8/Xh7gEq3uB6QJDNc5pQBWojKdHetOs= github.com/rickb777/date v1.19.1 h1:IMcFlWY3PagAcc274tJAag84+dh4ihusPxhu4jaHMwY= github.com/rickb777/date v1.19.1/go.mod h1:NxzFOW9ZWNeOWWE2kUXLDN59GSuGMsu3E4YVVk+GcVU= @@ -468,6 +470,8 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b h1:2n253B2r0pYSmEV+UNCQoPfU/FiaizQEK5Gu4Bq4JE8= +golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=