diff --git a/VERSION b/VERSION index 027934e..a8839f7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.11.1 \ No newline at end of file +0.11.2 \ No newline at end of file diff --git a/setup-x86.iss b/setup-x86.iss index 5f48561..03cf067 100644 --- a/setup-x86.iss +++ b/setup-x86.iss @@ -1,5 +1,5 @@ #define MyAppName "Tactical RMM Agent" -#define MyAppVersion "0.11.1" +#define MyAppVersion "0.11.2" #define MyAppPublisher "wh1te909" #define MyAppURL "https://github.com/wh1te909" #define MyAppExeName "tacticalrmm.exe" @@ -80,8 +80,8 @@ var StartTactical: string; StartCheckrunner: string; begin - StartTactical := ExpandConstant(' /c "{app}\{#NSSM}"' + ' start tacticalagent && ping 127.0.0.1 -n 2'); - StartCheckrunner := ExpandConstant(' /c "{app}\{#NSSM}"' + ' start checkrunner && ping 127.0.0.1 -n 2'); + StartTactical := ExpandConstant(' /c "{app}\{#NSSM}"' + ' start tacticalagent && ping 127.0.0.1 -n 7'); + StartCheckrunner := ExpandConstant(' /c "{app}\{#NSSM}"' + ' start checkrunner && ping 127.0.0.1 -n 3'); Exec('cmd.exe', StartTactical, '', SW_HIDE, ewWaitUntilTerminated, ResultCode); Exec('cmd.exe', StartCheckrunner, '', SW_HIDE, ewWaitUntilTerminated, ResultCode); end; diff --git a/setup.iss b/setup.iss index 7e5cce7..6954aed 100644 --- a/setup.iss +++ b/setup.iss @@ -1,5 +1,5 @@ #define MyAppName "Tactical RMM Agent" -#define MyAppVersion "0.11.1" +#define MyAppVersion "0.11.2" #define MyAppPublisher "wh1te909" #define MyAppURL "https://github.com/wh1te909" #define MyAppExeName "tacticalrmm.exe" @@ -80,8 +80,8 @@ var StartTactical: string; StartCheckrunner: string; begin - StartTactical := ExpandConstant(' /c "{app}\{#NSSM}"' + ' start tacticalagent && ping 127.0.0.1 -n 2'); - StartCheckrunner := ExpandConstant(' /c "{app}\{#NSSM}"' + ' start checkrunner && ping 127.0.0.1 -n 2'); + StartTactical := ExpandConstant(' /c "{app}\{#NSSM}"' + ' start tacticalagent && ping 127.0.0.1 -n 7'); + StartCheckrunner := ExpandConstant(' /c "{app}\{#NSSM}"' + ' start checkrunner && ping 127.0.0.1 -n 3'); Exec('cmd.exe', StartTactical, '', SW_HIDE, ewWaitUntilTerminated, ResultCode); Exec('cmd.exe', StartCheckrunner, '', SW_HIDE, ewWaitUntilTerminated, ResultCode); end; diff --git a/winagent/agent.py b/winagent/agent.py index 1688a99..7020436 100644 --- a/winagent/agent.py +++ b/winagent/agent.py @@ -26,6 +26,7 @@ import win32evtlogutil import winerror import wmi +from playhouse.migrate import SqliteMigrator, migrate from win32com.client import GetObject from utils import ( @@ -48,6 +49,7 @@ class AgentStorage(peewee.Model): agentpk = peewee.IntegerField() salt_master = peewee.CharField() salt_id = peewee.CharField() + cert = peewee.CharField(null=True) class Meta: database = db @@ -60,13 +62,14 @@ def __init__(self, log_level="INFO", log_to="file"): self.hostname = socket.gethostname() self.platform = platform.system().lower() self.arch = "64" if platform.machine().endswith("64") else "32" - self.load_db() self.programdir = os.path.join(os.environ["ProgramFiles"], "TacticalAgent") self.exe = os.path.join(self.programdir, "tacticalrmm.exe") self.system_drive = os.environ["SystemDrive"] self.salt_call = os.path.join(self.system_drive, "\\salt\\salt-call.bat") - self.setup_logging() + self.verify = None self.version = self.get_agent_version() + self.setup_logging() + self.load_db() @property def salt_minion_exe(self): @@ -98,7 +101,17 @@ def nssm(self): def load_db(self): if os.path.exists(db_path): - self.astor = self.get_db() + try: + self.astor = self.get_db() + self.verify = self.astor.cert + except: + self.logger.info("Migrating DB") + cert = peewee.CharField(null=True) + migrator = SqliteMigrator(db) + migrate(migrator.add_column("agentstorage", "cert", cert)) + self.load_db() + return + self.headers = { "content-type": "application/json", "Authorization": f"Token {self.astor.token}", @@ -202,6 +215,7 @@ async def script_check(self, data): json.dumps(payload), headers=self.headers, timeout=15, + verify=self.verify, ).json() if status == "failing" and data["assigned_tasks"]: @@ -258,6 +272,7 @@ async def ping_check(self, data): json.dumps(payload), headers=self.headers, timeout=15, + verify=self.verify, ).json() self.logger.debug(status) @@ -306,6 +321,7 @@ async def disk_check(self, data, exists=True): json.dumps(payload), headers=self.headers, timeout=15, + verify=self.verify, ).json() self.logger.debug(status) @@ -344,6 +360,7 @@ async def cpu_load_check(self, data, interval=7): json.dumps(payload), headers=self.headers, timeout=15, + verify=self.verify, ).json() self.logger.debug(status) @@ -377,6 +394,7 @@ async def mem_check(self, data): json.dumps(payload), headers=self.headers, timeout=15, + verify=self.verify, ).json() self.logger.debug(status) @@ -420,6 +438,7 @@ async def win_service_check(self, data, exists=True): json.dumps(payload), headers=self.headers, timeout=70, + verify=self.verify, ).json() self.logger.debug(status) @@ -526,6 +545,7 @@ async def event_log_check(self, data): json.dumps(payload), headers=self.headers, timeout=45, + verify=self.verify, ).json() self.logger.debug(status) @@ -783,7 +803,7 @@ def force_kill_salt(self): def update_salt(self): try: get = f"{self.astor.server}/api/v2/{self.astor.agentid}/saltminion/" - r = requests.get(get, headers=self.headers, timeout=15) + r = requests.get(get, headers=self.headers, timeout=15, verify=self.verify) if r.status_code != 200: self.logger.error(r.status_code) return @@ -883,7 +903,13 @@ def update_salt(self): put = f"{self.astor.server}/api/v2/saltminion/" payload = {"ver": latest_ver, "agent_id": self.astor.agentid} - r = requests.put(put, json.dumps(payload), headers=self.headers, timeout=30) + r = requests.put( + put, + json.dumps(payload), + headers=self.headers, + timeout=30, + verify=self.verify, + ) if r.status_code != 200: self.logger.error(r.status_code) @@ -941,7 +967,9 @@ def recover_mesh(self): try: mesh_info = f"{self.astor.server}/api/v1/{self.astor.agentpk}/meshinfo/" - resp = requests.get(mesh_info, headers=self.headers, timeout=15) + resp = requests.get( + mesh_info, headers=self.headers, timeout=15, verify=self.verify + ) except Exception: self._mesh_service_action("start") return @@ -950,7 +978,11 @@ def recover_mesh(self): if node_hex != resp.json(): payload = {"nodeidhex": node_hex} requests.patch( - mesh_info, json.dumps(payload), headers=self.headers, timeout=15 + mesh_info, + json.dumps(payload), + headers=self.headers, + timeout=15, + verify=self.verify, ) self._mesh_service_action("start") @@ -1199,7 +1231,11 @@ def get_all(self, obj): url = f"{self.astor.server}/api/v2/sysinfo/" try: r = requests.patch( - url, json.dumps(payload), headers=self.headers, timeout=15 + url, + json.dumps(payload), + headers=self.headers, + timeout=15, + verify=self.verify, ) except: pass diff --git a/winagent/checkrunner.py b/winagent/checkrunner.py index 8156e9d..c25ece9 100644 --- a/winagent/checkrunner.py +++ b/winagent/checkrunner.py @@ -16,7 +16,9 @@ def __init__(self, log_level, log_to): def get_checks(self): try: - resp = requests.get(self.checkrunner, headers=self.headers, timeout=15) + resp = requests.get( + self.checkrunner, headers=self.headers, timeout=15, verify=self.verify + ) except Exception as e: self.logger.debug(e) return False diff --git a/winagent/installer.py b/winagent/installer.py index ea42d7a..51d65bf 100644 --- a/winagent/installer.py +++ b/winagent/installer.py @@ -30,6 +30,8 @@ def __init__( auth_token, local_salt, local_mesh, + cert, + cmd_timeout, log_level, log_to="stdout", ): @@ -47,7 +49,8 @@ def __init__( self.log_to = log_to self.local_salt = local_salt self.local_mesh = local_mesh - self.mesh_success = True + self.cert = cert + self.cmd_timeout = cmd_timeout if cmd_timeout else 900 def install(self): # check for existing installation and exit if found @@ -156,6 +159,7 @@ def install(self): headers=token_headers, stream=True, timeout=90, + verify=self.cert, ) except Exception as e: self.logger.error(e) @@ -191,6 +195,7 @@ def install(self): json.dumps({"agent_id": self.agent_id}), headers=token_headers, timeout=15, + verify=self.cert, ) except Exception as e: self.logger.error(e) @@ -211,7 +216,9 @@ def install(self): meshAgent.remove_mesh(exe=mesh) # install mesh - self.mesh_node_id = meshAgent.install_mesh(exe=mesh) + self.mesh_node_id = meshAgent.install_mesh( + exe=mesh, cmd_timeout=self.cmd_timeout + ) self.logger.debug(f"{self.mesh_node_id=}") sys.stdout.flush() @@ -236,6 +243,7 @@ def install(self): json.dumps(payload), headers=token_headers, timeout=60, + verify=self.cert, ) except Exception as e: self.logger.error(e) @@ -261,6 +269,7 @@ def install(self): agentpk=self.agent_pk, salt_master=self.salt_master, salt_id=self.salt_id, + cert=self.cert if self.cert else None, ).save() except Exception as e: self.logger.error(e) @@ -291,7 +300,7 @@ def install(self): try: install_salt = subprocess.run( - salt_cmd, cwd=self.programdir, shell=True, timeout=300 + salt_cmd, cwd=self.programdir, shell=True, timeout=self.cmd_timeout ) except Exception as e: self.logger.error(e) @@ -341,6 +350,7 @@ def install(self): json.dumps(payload), headers=self.headers, timeout=35, + verify=self.cert, ) except Exception as e: self.logger.debug(e) @@ -382,6 +392,7 @@ def install(self): json.dumps({"agent_id": self.agent_id}), headers=self.headers, timeout=30, + verify=self.cert, ) except Exception as e: self.logger.debug(e) diff --git a/winagent/mesh.py b/winagent/mesh.py index 5055964..ada799d 100644 --- a/winagent/mesh.py +++ b/winagent/mesh.py @@ -58,13 +58,15 @@ def remove_mesh(self, exe): if self.mesh_dir: remove_dir(self.mesh_dir) - def install_mesh(self, exe): + def install_mesh(self, exe, cmd_timeout): attempts = 0 retries = 5 print("Installing mesh agent", flush=True) try: - ret = subprocess.run([exe, "-fullinstall"], capture_output=True, timeout=60) + ret = subprocess.run( + [exe, "-fullinstall"], capture_output=True, timeout=cmd_timeout + ) except Exception as e: self.logger.error(e) sys.stdout.flush() diff --git a/winagent/tacticalrmm.py b/winagent/tacticalrmm.py index 73373b0..b04b337 100644 --- a/winagent/tacticalrmm.py +++ b/winagent/tacticalrmm.py @@ -12,6 +12,7 @@ def main(): parser.add_argument("--api", action="store", dest="api_url", type=str) parser.add_argument("--client-id", action="store", dest="client_id", type=int) parser.add_argument("--site-id", action="store", dest="site_id", type=int) + parser.add_argument("--timeout", action="store", dest="cmd_timeout", type=int) parser.add_argument( "--desc", action="store", @@ -63,6 +64,13 @@ def main(): type=str, help=r'The full path to the Mesh Agent executable e.g. "C:\\temp\\meshagent.exe"', ) + parser.add_argument( + "--cert", + action="store", + dest="cert", + type=str, + help=r'The full path to the local cert e.g. "C:\\temp\\ca.pem"', + ) args = parser.parse_args() if args.version: @@ -121,6 +129,26 @@ def main(): print("", flush=True) sys.exit(1) + if args.cert: + if not os.path.exists(args.cert): + parser.print_help() + sys.stdout.flush() + print(f"\nError: {args.cert} does not exist\n", flush=True) + sys.exit(1) + if not os.path.isfile(args.cert): + parser.print_help() + sys.stdout.flush() + print( + f"\nError: {args.cert} must be a file, not a folder.", + flush=True, + ) + print( + r'Make sure to use double backslashes for file paths, and double quotes e.g. "C:\\temp\\ca.pem"', + flush=True, + ) + print("", flush=True) + sys.exit(1) + from installer import Installer installer = Installer( @@ -136,6 +164,8 @@ def main(): log_level=args.log_level, local_salt=args.local_salt, local_mesh=args.local_mesh, + cert=args.cert, + cmd_timeout=args.cmd_timeout, ) installer.install() diff --git a/winagent/taskrunner.py b/winagent/taskrunner.py index 18a6563..75a6ebd 100644 --- a/winagent/taskrunner.py +++ b/winagent/taskrunner.py @@ -32,7 +32,9 @@ async def run_while_in_event_loop(self): def get_task(self): try: - resp = requests.get(self.task_url, headers=self.headers, timeout=15) + resp = requests.get( + self.task_url, headers=self.headers, timeout=15, verify=self.verify + ) except Exception as e: self.logger.debug(e) return False @@ -113,6 +115,7 @@ async def run_task(self, data): json.dumps(payload), headers=self.headers, timeout=15, + verify=self.verify, ) except Exception as e: diff --git a/winagent/winagentsvc.py b/winagent/winagentsvc.py index 3a78976..035c5fa 100644 --- a/winagent/winagentsvc.py +++ b/winagent/winagentsvc.py @@ -34,7 +34,11 @@ def run(self): self.logger.debug(info) r = requests.post( - self.hello, json.dumps(info), headers=self.headers, timeout=30 + self.hello, + json.dumps(info), + headers=self.headers, + timeout=30, + verify=self.verify, ) except Exception as e: self.logger.debug(e) @@ -61,6 +65,7 @@ def run(self): json.dumps(payload), headers=self.headers, timeout=30, + verify=self.verify, ) if isinstance(r.json(), dict) and "recovery" in r.json().keys(): diff --git a/winagent/winupdater.py b/winagent/winupdater.py index ff3d48b..10efc17 100644 --- a/winagent/winupdater.py +++ b/winagent/winupdater.py @@ -45,6 +45,7 @@ def trigger_patch_scan(self): data=json.dumps(payload), headers=self.headers, timeout=60, + verify=self.verify, ) except Exception as e: self.logger.debug(e) @@ -59,6 +60,7 @@ def install_all(self): data=json.dumps(self.check_payload), headers=self.headers, timeout=30, + verify=self.verify, ) except Exception as e: self.logger.debug(e) @@ -91,6 +93,7 @@ def install_all(self): json.dumps(res_payload), headers=self.headers, timeout=30, + verify=self.verify, ) # trigger a patch scan once all updates finish installing, and check if reboot needed