2022-03-19 18:55:43 +00:00
/ *
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"
"io"
"net/url"
"os"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"time"
"github.com/go-resty/resty/v2"
trmm "github.com/wh1te909/trmm-shared"
)
type Installer struct {
Headers map [ string ] string
RMM string
ClientID int
SiteID int
Description string
AgentType string
Power bool
RDP bool
Ping bool
Token string
LocalMesh string
Cert string
Proxy string
Timeout time . Duration
SaltMaster string
Silent bool
NoMesh bool
MeshDir string
MeshNodeID string
}
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 )
}
var arch string
switch a . Arch {
case "x86_64" :
arch = "64"
case "x86" :
arch = "32"
}
2022-06-15 22:03:13 +00:00
var installerMeshSystemBin string
2022-03-19 18:55:43 +00:00
if len ( i . MeshDir ) > 0 {
2022-06-15 22:03:13 +00:00
installerMeshSystemBin = filepath . Join ( i . MeshDir , "MeshAgent.exe" )
2022-03-19 18:55:43 +00:00
} else {
2022-06-15 22:03:13 +00:00
installerMeshSystemBin = a . MeshSystemBin
2022-03-19 18:55:43 +00:00
}
var meshNodeID string
if runtime . GOOS == "windows" && ! i . NoMesh {
mesh := filepath . Join ( a . ProgramDir , a . MeshInstaller )
if i . LocalMesh == "" {
a . Logger . Infoln ( "Downloading mesh agent..." )
payload := map [ string ] string { "arch" : arch , "plat" : a . Platform }
r , err := rClient . R ( ) . SetBody ( payload ) . SetOutput ( mesh ) . Post ( fmt . Sprintf ( "%s/api/v3/meshexe/" , baseURL ) )
if err != nil {
a . installerMsg ( fmt . Sprintf ( "Failed to download mesh agent: %s" , err . Error ( ) ) , "error" , i . Silent )
}
if r . StatusCode ( ) != 200 {
a . installerMsg ( fmt . Sprintf ( "Unable to download the mesh agent from the RMM. %s" , r . String ( ) ) , "error" , i . Silent )
}
} else {
err := copyFile ( i . LocalMesh , mesh )
if err != nil {
a . installerMsg ( err . Error ( ) , "error" , i . Silent )
}
}
a . Logger . Infoln ( "Installing mesh agent..." )
a . Logger . Debugln ( "Mesh agent:" , mesh )
time . Sleep ( 1 * time . Second )
2022-06-15 22:03:13 +00:00
meshNodeID , err = a . installMesh ( mesh , installerMeshSystemBin , i . Proxy )
2022-03-19 18:55:43 +00:00
if err != nil {
a . installerMsg ( fmt . Sprintf ( "Failed to install mesh agent: %s" , err . Error ( ) ) , "error" , i . Silent )
}
}
if len ( i . MeshNodeID ) > 0 {
meshNodeID = i . MeshNodeID
}
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" : meshNodeID ,
"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 ( )
if runtime . GOOS == "windows" {
// send software api
a . SendSoftware ( )
a . Logger . Debugln ( "Creating temp dir" )
a . CreateTRMMTempDir ( )
a . Logger . Debugln ( "Disabling automatic windows updates" )
a . PatchMgmnt ( true )
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 . Logger . Infoln ( "Adding windows defender exclusions" )
a . addDefenderExlusions ( )
if i . Power {
a . Logger . Infoln ( "Disabling sleep/hibernate..." )
DisableSleepHibernate ( )
}
if i . Ping {
a . Logger . Infoln ( "Enabling ping..." )
EnablePing ( )
}
if i . RDP {
a . Logger . Infoln ( "Enabling RDP..." )
EnableRDP ( )
}
}
a . installerMsg ( "Installation was successfull!\nAllow a few minutes for the agent to properly display in the RMM" , "info" , i . Silent )
}
func copyFile ( src , dst string ) error {
in , err := os . Open ( src )
if err != nil {
return err
}
defer in . Close ( )
out , err := os . Create ( dst )
if err != nil {
return err
}
defer out . Close ( )
_ , err = io . Copy ( out , in )
if err != nil {
return err
}
return nil
}