Compare commits

...

350 Commits

Author SHA1 Message Date
wh1te909
3588bf827b Release 0.11.0 2022-01-13 01:31:45 +00:00
wh1te909
d66a41a8a3 bump versions 2022-01-12 23:24:52 +00:00
wh1te909
90914bff14 bump mesh 2022-01-12 21:42:40 +00:00
Dan
62414848f4 Merge pull request #930 from silversword411/develop
docs - av eset
2022-01-12 13:11:25 -08:00
Dan
d4ece6ecd7 Merge pull request #929 from iamkhris/develop
Add files via upload
2022-01-12 13:11:11 -08:00
silversword411
d1ec60bb63 docs - av eset 2022-01-12 15:31:34 -05:00
Christopher Phillips
4f672c736b Add files via upload
Sends Windows 10 Toast alert when password expiration reaches 7, 3, 2, and 1 days.  Works with both local and domain accounts.  Best to setup as a scheduled task, but can also be run manually.  On 1 day alert, an "Urgent" BurntToastLogo is downloaded and used instead of the regular logo to indicate importance.  These files are hosted on a site you have access to.
2022-01-12 09:51:06 -07:00
Dan
2e5c351d8b Merge pull request #927 from silversword411/develop
docs - av and faq additions
2022-01-11 14:14:55 -08:00
silversword411
3562553346 Merge branch 'develop' of https://github.com/silversword411/tacticalrmm into develop 2022-01-11 16:58:35 -05:00
silversword411
4750b292a5 docs - av, faq additions 2022-01-11 16:58:33 -05:00
Dan
3eb0561e90 Merge pull request #925 from yaroz/patch-1
Get Anydesk ID for licensed or unlicensed installs.
2022-01-11 09:39:59 -08:00
Dan
abb118c8ca Merge pull request #924 from lcsnetworks/mesh_smtp_settings
Make Mesh SMTP settings configurable
2022-01-11 09:38:53 -08:00
wh1te909
2818a229b6 fix bit to string 2022-01-11 03:16:11 +00:00
sadnub
a9b8af3677 fix last day of month 2022-01-10 20:28:26 -05:00
yaroz
0354da00da Update Win_AnyDesk_Get_Anynet_ID.ps1
Licensed AnyDesk installs do not save system.conf file to the $GoodPath\AnyDesk directory.  Searches subdirectories of $GoodPath for system.conf and uses the result to get the ID from.
2022-01-10 16:07:35 -05:00
Joel DeTeves
b179587475 Make Mesh SMTP settings configurable 2022-01-10 12:53:33 -08:00
wh1te909
3021f90bc5 agent ver check 2022-01-10 20:00:11 +00:00
wh1te909
a14b0278c8 remove dead code for unsupported agents 2022-01-10 19:32:48 +00:00
wh1te909
80070b333e fix exception in automation svc manual mode 2022-01-10 18:08:35 +00:00
Dan
3aa8dcac11 Merge pull request #923 from silversword411/develop
docs - docker 2fa reset
2022-01-10 09:34:51 -08:00
silversword411
e920f05611 docs - docker 2fa reset 2022-01-10 09:24:15 -05:00
wh1te909
3594afd3aa update reqs 2022-01-10 08:49:22 +00:00
wh1te909
9daaee8212 demo stuff 2022-01-10 08:37:30 +00:00
wh1te909
d022707349 remove unused imports 2022-01-10 08:09:36 +00:00
wh1te909
3948605ae6 remove deprecated endpoint 2022-01-10 08:00:20 +00:00
wh1te909
f2ded5fdd6 fix tz aware 2022-01-10 07:26:27 +00:00
wh1te909
00b47be181 add loading 2022-01-10 06:07:56 +00:00
sadnub
a2fac5d946 changes nats file watcher to use the NATS_CONFIG variable 2022-01-09 21:36:21 -05:00
sadnub
a00b5bb36b convert old scheduled type tasks to daily 2022-01-09 21:25:26 -05:00
wh1te909
d4fbc34085 fix collector task button when editing task 2022-01-10 01:49:59 +00:00
wh1te909
e9e3031992 add migration 2022-01-10 01:13:41 +00:00
sadnub
c2c7553f56 convert task to new format 2022-01-09 16:04:00 -05:00
sadnub
4e60cb89c9 fix remove if not scheduled 2022-01-09 12:17:07 -05:00
sadnub
ec4523240f remove inotify installation from nats docker image 2022-01-09 11:49:07 -05:00
sadnub
1655ddbcaa fix/add tests and add additional serverside validation for tasks 2022-01-09 11:41:52 -05:00
sadnub
997c677f30 wip 2022-01-08 21:46:32 -05:00
wh1te909
d5fc8a2d7e reduce the watcher interval to 1 second to prevent nats auth errors on first check-in 2022-01-09 01:11:57 +00:00
wh1te909
3bcd0302a8 fix django admin error 2022-01-09 01:02:31 +00:00
wh1te909
de91b7e8af update reqs 2022-01-08 10:34:02 +00:00
Dan
7efd1d7c9e Merge pull request #921 from silversword411/develop
docs - sophos and index update
2022-01-08 02:02:10 -08:00
Dan
b5151a2178 Merge pull request #920 from lcsnetworks/nats_config_watcher
Replace inotify with custom config watcher script
2022-01-08 02:01:21 -08:00
silversword411
c8432020c6 docs - Add Script Variables to Index 2022-01-07 12:35:05 -05:00
wh1te909
2c9d413a1a update nats docker 2022-01-07 08:04:37 +00:00
Joel DeTeves
cdf842e7ad Make NATS config watcher interval adjustable 2022-01-06 23:15:11 -08:00
Joel DeTeves
c917007949 Replace inotify with custom config watcher script 2022-01-06 22:49:01 -08:00
wh1te909
64278c6b3c update reqs 2022-01-07 06:46:55 +00:00
wh1te909
10a01ed14a add migration 2022-01-07 06:44:24 +00:00
sadnub
ba3bd1407b fix cert path in dev and prod 2022-01-06 09:56:33 -05:00
sadnub
73666c9a04 remove check for cert path exists for docker 2022-01-06 09:35:54 -05:00
sadnub
eae24083c9 fix docker dev with latest prod docker image options 2022-01-05 21:17:59 -05:00
silversword411
a644510c27 docs - Sophos rules 2022-01-05 11:12:14 -05:00
Dan
57859d0da2 Merge pull request #910 from silversword411/develop
docs - script temp, support, updating script run info, mesh agent blank data
2022-01-03 13:28:31 -08:00
silversword411
057f0ff648 docs - support template typo 2022-01-03 12:00:10 -05:00
sadnub
05d1c867f2 Merge pull request #911 from lcsnetworks/mesh-ws-mask-override
Add option to enable webSocketMaskOverride in MeshCentral config
2022-01-01 11:49:31 -05:00
Joel DeTeves
a2238fa435 Add option to enable webSocketMaskOverride in MeshCentral config 2021-12-31 19:31:18 -08:00
silversword411
12b7426a7c docs - mesh agent blank data 2021-12-31 17:38:47 -05:00
silversword411
5148d613a7 docs - updating script run info 2021-12-31 17:23:09 -05:00
sadnub
f455c15882 fix task edits not involving task_type 2021-12-31 15:11:33 -05:00
silversword411
618fdabd0e docs - support template update 2021-12-31 14:15:24 -05:00
silversword411
3b69e2896c docs - script temp location 2021-12-31 13:38:13 -05:00
sadnub
7306b63ab1 set some default values for task settings 2021-12-31 13:33:59 -05:00
sadnub
7e3133caa2 automated task rework 2021-12-30 23:56:37 -05:00
sadnub
560901d714 Merge pull request #909 from lcsnetworks/fix_get_mesh_exe_url
Update get_mesh_exe_url command to use MESH_WS_URL for docker
2021-12-30 20:19:20 -05:00
Joel DeTeves
166ce9ae78 Update get_mesh_exe_url command to use MESH_WS_URL for docker 2021-12-30 15:53:31 -08:00
Dan
d3395a685e Merge pull request #908 from silversword411/develop
Docs - update faq and mesh agent recovery
2021-12-30 13:55:31 -08:00
silversword411
6d5e9a8566 docs - agent repair 2021-12-30 16:43:05 -05:00
silversword411
69ec03feb4 docs - faq updates 2021-12-30 16:42:50 -05:00
sadnub
f92982cd5a fix quotes around cert path in tactical docker image 2021-12-30 08:50:10 -05:00
sadnub
5570f2b464 Merge pull request #907 from lcsnetworks/set_ssl_path
Make cert pub & private paths configurable
2021-12-29 21:56:25 -05:00
Joel DeTeves
ad19dc0240 Make cert pub & private paths configurable 2021-12-29 18:01:37 -08:00
sadnub
9b1d4faff8 fix typo in nginx config 2021-12-29 09:26:15 -05:00
Dan
76756d20e9 Merge pull request #904 from silversword411/develop
docs - nats-api update
2021-12-28 15:50:30 -08:00
sadnub
e564500480 update mesh initial setup command to use MESH_WS_URL for docker 2021-12-28 00:35:02 -05:00
sadnub
19c15ce58d Add configurable mesh websocket url 2021-12-28 00:31:35 -05:00
sadnub
a027785098 Merge pull request #905 from lcsnetworks/nginx_config_vars
Replace hardcoded services & DNS resolvers in NGINX config with variables
2021-12-28 00:27:02 -05:00
Joel DeTeves
36a9f10aae Replace hardcoded services in NGINX config with variables 2021-12-27 15:46:11 -08:00
silversword411
99a11a4b53 docs - nats-api update 2021-12-27 17:01:05 -05:00
wh1te909
55cac4465c speed up client/site tree loading and add popout button to summary tab 2021-12-26 09:36:56 +00:00
wh1te909
ff395fd074 update api docs 2021-12-24 21:15:43 +00:00
wh1te909
972b6e09c7 update docs fixes #896 2021-12-24 21:00:58 +00:00
Dan
e793a33b15 Merge pull request #901 from silversword411/develop
docs - enable swagger
2021-12-24 12:04:04 -08:00
silversword411
e70d4ff3f3 docs - api swagger tweaks 2021-12-24 14:50:02 -05:00
silversword411
cd0635d3a0 docs - enable swagger 2021-12-24 11:37:36 -05:00
wh1te909
81702d8595 update middleware method 2021-12-23 23:07:19 +00:00
sadnub
aaa4a65b04 fix tests 2021-12-23 14:47:44 -05:00
sadnub
430797e626 add take control button to summary 2021-12-23 14:44:04 -05:00
sadnub
d454001f49 fixed #874 2021-12-23 14:00:05 -05:00
sadnub
bd90ee1f58 added agent status page and added #332 2021-12-23 13:57:37 -05:00
wh1te909
196aaa5427 more demo stuff 2021-12-23 07:15:34 +00:00
wh1te909
6e42233b33 fix export button not working fixes #895 2021-12-22 22:10:52 +00:00
Dan
8e44df8525 Merge pull request #894 from MalteKiefer/feature/script-update-lenovo-drivers
added script for lenovo driver updates
2021-12-22 12:03:00 -08:00
wh1te909
a8a1536941 black 2021-12-22 19:58:09 +00:00
silversword411
99d1728c70 scripts_wip - working on newer version w/features 2021-12-22 12:57:55 -05:00
Malte Kiefer
6bbb92cdb9 move script to correct location 2021-12-22 16:30:35 +01:00
Malte Kiefer
b80e7c06bf added script for lenovo driver updates 2021-12-22 15:47:52 +01:00
wh1te909
bf467b874c make site dropdown required 2021-12-22 06:19:28 +00:00
wh1te909
43c9f6be56 add script to generate fake agents for demo 2021-12-22 06:15:40 +00:00
Dan
6811a4f4ae Merge pull request #890 from silversword411/develop
docs - link add
2021-12-21 22:13:18 -08:00
silversword411
1f16dd9c43 docs - av 2021-12-22 00:27:29 -05:00
silversword411
63a43ce104 docs - link add 2021-12-21 22:23:26 -05:00
wh1te909
bd7ce5417e add demo middleware 2021-12-22 02:34:36 +00:00
Dan
941ee54a97 Merge pull request #889 from silversword411/develop
docs - av exclusions
2021-12-21 11:04:42 -08:00
wh1te909
a5d4a64f47 nginx conf updates for older installs fixes #888 2021-12-21 19:04:00 +00:00
silversword411
d96fcd4a98 docs - av exclusions 2021-12-21 12:26:24 -05:00
Dan
de42e2f747 Merge pull request #887 from silversword411/develop
docs - sc and install server, and script_wip
2021-12-20 16:59:59 -08:00
silversword411
822a93aeb6 docs - sc and install server, and script_wip 2021-12-20 16:06:54 -05:00
Dan
c31b4aaeff Merge pull request #886 from silversword411/develop
script_wip - admin LAPS script
2021-12-20 10:51:01 -08:00
Dan
8c9a386054 Merge pull request #884 from MalteKiefer/feature/script-get-securepoint-deviceid
added script to get securepoint deviceid
2021-12-20 10:50:46 -08:00
silversword411
8c90933615 docs - fix TOC and typo 2021-12-20 12:35:43 -05:00
silversword411
6f8c242333 script_wip - admin LAPS script 2021-12-20 12:20:43 -05:00
Malte Kiefer
fe8b66873a added script to get securepoint deviceid 2021-12-20 10:32:08 +01:00
wh1te909
00c5f1365a bump versions 2021-12-20 06:48:02 +00:00
wh1te909
f7d317328a update reqs 2021-12-20 06:48:02 +00:00
wh1te909
3ccd705225 bump backup script version 2021-12-20 06:48:02 +00:00
diskraider
9e439fffaa Change timeout method
The current timeout results in an error "ERROR: Input redirection is not supported, exiting the process immediately.".

Reusing the ping tool to act as a timeout resolves this error because the batch script is not producing a user interruptable timeout but will still produce a 4-5 second timeout.
2021-12-20 06:48:02 +00:00
wh1te909
859dc170e7 update uninstall params 2021-12-20 06:48:02 +00:00
silversword411
1932d8fad9 docs - backup and silent uninstall tweaks 2021-12-20 06:48:02 +00:00
wh1te909
0c814ae436 reduce ram reqs 2021-12-20 06:48:02 +00:00
sadnub
89313d8a37 make post_update_tasks run on init container start 2021-12-20 06:48:02 +00:00
silversword411
2b85722222 docs - mesh download multiple 2021-12-20 06:48:02 +00:00
David Randall
57e5b0188c Fixes #872: backup.sh does not have EOL
Add EOL to backup.sh so CRON doesn't fail.
2021-12-20 06:48:02 +00:00
silversword411
2d7c830e70 docs code signing emphasis 2021-12-20 06:48:02 +00:00
silversword411
ccaa1790a9 docs - sys req info 2021-12-20 06:48:02 +00:00
silversword411
f6531d905e docs cron backups 2021-12-20 06:48:02 +00:00
silversword411
64a31879d3 docs - video of updating server 2021-12-20 06:48:02 +00:00
silversword411
0c6a4b1ed2 script - tweak AUOptions revert 2021-12-20 06:48:02 +00:00
silversword411
67801f39fe docs - 2rd party Screenconnect AIO 2021-12-20 06:48:02 +00:00
silversword411
892a0d67bf docs updating install agent script 2021-12-20 06:48:02 +00:00
silversword411
9fc0b7d5cc script_wip 2021-12-20 06:48:02 +00:00
bc24fl
22a614ef54 Added Printer Restart Jobs Community Script 2021-12-20 06:48:02 +00:00
silversword411
cd257b8e4d docs faq log4j 2021-12-20 06:48:02 +00:00
silversword
fa1ee2ca14 docs - updating index 2021-12-20 06:48:02 +00:00
wh1te909
34ea1adde6 sorting fixes #857 2021-12-20 06:48:02 +00:00
wh1te909
41cf8abb1f update reqs 2021-12-20 06:48:02 +00:00
silversword411
c0ffec1a4c docs - howitallworks nats server service 2021-12-20 06:48:02 +00:00
bc24fl
65779b8eaf Added Sophos Endpoint Install Community Script 2021-12-20 06:48:02 +00:00
bc24fl
c47bdb2d56 Added Sophos Endpoint Install Community Script 2021-12-20 06:48:02 +00:00
Michael Maertzdorf
d47ae642e7 Create SECURITY.md 2021-12-20 06:48:02 +00:00
Michael Maertzdorf
39c4609cc6 Create devskim-analysis.yml 2021-12-20 06:48:02 +00:00
dependabot[bot]
3ebba02a10 Bump django from 3.2.9 to 3.2.10 in /api/tacticalrmm
Bumps [django](https://github.com/django/django) from 3.2.9 to 3.2.10.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/3.2.9...3.2.10)

---
updated-dependencies:
- dependency-name: django
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-20 06:48:02 +00:00
Michael Maertzdorf
4dc7a96e79 Create codeql-analysis.yml 2021-12-20 06:48:02 +00:00
silversword411
5a49a29110 docs - nginx proxy info 2021-12-20 06:48:02 +00:00
wh1te909
983a5c2034 bump versions 2021-12-20 05:47:00 +00:00
wh1te909
15829f04a3 update reqs 2021-12-20 05:46:12 +00:00
wh1te909
934618bc1c bump backup script version 2021-12-20 04:27:12 +00:00
Dan
2c5ec75b88 Merge pull request #881 from diskraider/patch-1
Change timeout method
2021-12-19 19:56:03 -08:00
diskraider
df11fd744f Change timeout method
The current timeout results in an error "ERROR: Input redirection is not supported, exiting the process immediately.".

Reusing the ping tool to act as a timeout resolves this error because the batch script is not producing a user interruptable timeout but will still produce a 4-5 second timeout.
2021-12-19 22:10:14 -05:00
wh1te909
4dba0fb43d update uninstall params 2021-12-20 00:48:55 +00:00
Dan
7a0d86b8dd Merge pull request #878 from silversword411/develop
docs - backup and silent uninstall tweaks
2021-12-19 16:42:53 -08:00
silversword411
a94cd98e0f docs - backup and silent uninstall tweaks 2021-12-19 13:48:25 -05:00
wh1te909
8e95e51edc reduce ram reqs 2021-12-19 00:36:32 +00:00
sadnub
6f1b00284a make post_update_tasks run on init container start 2021-12-17 18:25:53 -05:00
Dan
58549a6cac Merge pull request #875 from silversword411/develop
docs - mesh download multiple
2021-12-17 14:15:41 -08:00
silversword411
acc9a6118f docs - mesh download multiple 2021-12-17 17:04:50 -05:00
Dan
c7811e861c Merge pull request #873 from NiceGuyIT/pr-872
Fixes #872: backup.sh does not have EOL
2021-12-16 15:48:28 -08:00
Dan
55cf766ff0 Merge pull request #871 from silversword411/develop
docs - 3rd party Screenconnect AIO, update video, cron backups, sys reqs. Script update windows update settings removal
2021-12-16 15:48:13 -08:00
silversword411
a1eaf38324 docs code signing emphasis 2021-12-16 18:10:01 -05:00
silversword411
c6788092d3 docs - sys req info 2021-12-16 14:58:15 -05:00
David Randall
f89f74ef3f Fixes #872: backup.sh does not have EOL
Add EOL to backup.sh so CRON doesn't fail.
2021-12-16 14:22:43 -05:00
silversword411
3e40f02001 docs cron backups 2021-12-16 14:19:23 -05:00
silversword411
c169967c1b docs - video of updating server 2021-12-16 13:17:16 -05:00
silversword411
2830e7c569 script - tweak AUOptions revert 2021-12-16 12:57:09 -05:00
silversword411
415f08ba3a docs - 2rd party Screenconnect AIO 2021-12-16 12:27:24 -05:00
Dan
d726bcdc19 Merge pull request #866 from silversword411/develop
docs: gpo script update and wip script for api example
2021-12-14 12:12:41 -08:00
silversword411
f259c25a70 docs updating install agent script 2021-12-14 13:20:36 -05:00
silversword411
4db937cf1f Merge branch 'develop' of https://github.com/silversword411/tacticalrmm into develop 2021-12-14 10:42:22 -05:00
silversword411
dad9d0660c script_wip 2021-12-14 10:42:15 -05:00
Dan
0c450a5bb2 Merge pull request #863 from silversword411/develop
docs faq log4j
2021-12-13 23:25:23 -08:00
silversword411
ef59819c01 Merge branch 'develop' of https://github.com/silversword411/tacticalrmm into develop 2021-12-13 22:07:02 -05:00
silversword411
c651e7c84b docs faq log4j 2021-12-13 22:07:00 -05:00
Dan
20b8debb1c Merge pull request #862 from silversword411/develop
docs - updating index
2021-12-13 17:52:16 -08:00
Dan
dd5743f0a1 Merge pull request #861 from bc24fl/develop
Added Printer Restart Jobs Community Script
2021-12-13 17:51:57 -08:00
silversword
7da2b51fae docs - updating index 2021-12-13 20:12:09 -05:00
bc24fl
0236800392 Added Printer Restart Jobs Community Script 2021-12-13 19:52:10 -05:00
wh1te909
4f822878f7 sorting fixes #857 2021-12-13 22:50:16 +00:00
wh1te909
c2810e5fe5 update reqs 2021-12-13 22:49:26 +00:00
Dan
b89ba4b801 Merge pull request #860 from silversword411/develop
docs - howitallworks nats server service
2021-12-13 00:09:48 -08:00
silversword411
07c680b839 docs - howitallworks nats server service 2021-12-13 03:02:14 -05:00
Dan
fd50db4eab Merge pull request #856 from bc24fl/develop
Added Sophos Endpoint Install Community Script
2021-12-12 23:08:50 -08:00
Dan
0ee95b36a6 Merge pull request #853 from Data4ITBV/develop
Vulnerability fix
2021-12-12 23:08:25 -08:00
Dan
b8cf07149e Merge pull request #852 from silversword411/develop
docs - nginx proxy info
2021-12-12 23:01:05 -08:00
silversword411
1b699f1a87 Merge branch 'wh1te909:develop' into develop 2021-12-12 13:46:10 -05:00
Michael Maertzdorf
d3bfd238d3 Create SECURITY.md 2021-12-12 01:54:18 +01:00
Michael Maertzdorf
1f43abb3c8 Create devskim-analysis.yml 2021-12-12 01:44:04 +01:00
bc24fl
287c753e4a Added Sophos Endpoint Install Community Script 2021-12-11 13:34:53 -05:00
bc24fl
8a5374d31a Added Sophos Endpoint Install Community Script 2021-12-11 13:31:39 -05:00
Michael Maertzdorf
e219eaa934 Merge pull request #1 from Data4ITBV/dependabot/pip/api/tacticalrmm/django-3.2.10
Bump django from 3.2.9 to 3.2.10 in /api/tacticalrmm
2021-12-11 11:49:27 +01:00
Michael Maertzdorf
fd314480ca Create codeql-analysis.yml 2021-12-11 11:48:49 +01:00
dependabot[bot]
dd45396cf3 Bump django from 3.2.9 to 3.2.10 in /api/tacticalrmm
Bumps [django](https://github.com/django/django) from 3.2.9 to 3.2.10.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/3.2.9...3.2.10)

---
updated-dependencies:
- dependency-name: django
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-11 10:48:42 +00:00
sadnub
1e2a56c5e9 Release 0.10.4 2021-12-10 21:59:35 -05:00
sadnub
8011773af4 bump versions 2021-12-10 19:12:45 -05:00
sadnub
ddc69c692e formatting 2021-12-10 19:09:52 -05:00
sadnub
df925c9744 fix script tests 2021-12-10 19:04:20 -05:00
sadnub
1726341aad remove script hashing since it was erroring out on some characters 2021-12-10 18:51:32 -05:00
sadnub
63b1ccc7a7 fix deleted community scripts not being removed from database 2021-12-10 18:50:51 -05:00
silversword411
ee5db31518 docs - nginx proxy info 2021-12-10 16:21:53 -05:00
Dan
e80397c857 Merge pull request #847 from silversword411/develop
Community scripts - adding software install report, parameters to task scheduler, bluescreen report
2021-12-09 09:25:03 -08:00
silversword411
81aa7ca1a4 community scripts - user enable/disable 2021-12-09 01:44:40 -05:00
silversword411
f0f7695890 community script - Windows Update revert to MS Auto managed 2021-12-09 01:16:49 -05:00
silversword411
e7e8ce2f7a community script - adding chocolately list installed 2021-12-09 01:09:47 -05:00
silversword411
ba37a3f18d script library - task scheduler adding parameters 2021-12-09 01:00:41 -05:00
silversword411
60b11a7a5d community scripts - new user monitor add parameters 2021-12-09 00:56:13 -05:00
silversword411
29461c20a7 script library - Bluescreen Report 2021-12-09 00:50:43 -05:00
silversword411
2ff1f34543 Community scripts - adding software install report 2021-12-09 00:40:37 -05:00
wh1te909
b75d7f970f use getattr with a default for optional settings 2021-12-09 00:08:49 +00:00
wh1te909
204681f097 fix openfile limit with 1k+ agents 2021-12-09 00:06:33 +00:00
wh1te909
e239fe95a4 remove old checks from update script 2021-12-08 18:37:44 +00:00
Dan
0a101f061a Merge pull request #844 from silversword411/develop
adding docs tips n trick and fixing PR #833
2021-12-07 20:03:07 -08:00
silversword411
f112a17afa Fixing community scripts and docs from PR #833 2021-12-07 22:29:37 -05:00
silversword411
54658a66d2 docs - Adding tips n tricks 2021-12-07 22:12:44 -05:00
sadnub
6b8f5a76e4 Merge pull request #833 from r3die/develop
Splashtop 3rd party integration docs and script
2021-12-07 19:47:52 -05:00
Dan
623a5d338d Merge pull request #842 from silversword411/develop
Adding Repo to Help menu
2021-12-06 20:30:38 -08:00
silversword411
9c5565cfd5 Merge branch 'develop' of https://github.com/silversword411/tacticalrmm into develop 2021-12-06 22:50:12 -05:00
silversword411
722f2efaee Adding Github repo to Help menu 2021-12-06 22:49:45 -05:00
Dan
4928264204 Merge pull request #841 from silversword411/develop
docs update: mgmt commands
2021-12-04 01:14:20 -08:00
silversword411
12d62ddc2a docs - adding mgmt commands for docker 2021-12-03 11:13:44 -05:00
wh1te909
da54e97217 Release 0.10.3 2021-12-02 08:19:38 +00:00
wh1te909
9c0993dac8 bump version 2021-12-02 07:50:52 +00:00
wh1te909
175486b7c4 fix bug where reboot_required field was not being updated when agent didn't have a patch policy and setting was set to 'inherit' 2021-12-02 01:39:41 +00:00
wh1te909
4760a287f6 update docs 2021-12-01 19:53:37 +00:00
wh1te909
0237b48c87 update reqs 2021-12-01 19:37:53 +00:00
Dan
95c9f22e6c Merge pull request #837 from silversword411/develop
docs tweak
2021-12-01 09:31:22 -08:00
silversword411
9b001219d5 docs tweak 2021-12-01 12:27:52 -05:00
Dan
6ff15efc7b Merge pull request #835 from silversword411/develop
docs outbound firewall rules
2021-11-30 16:07:56 -08:00
silversword411
6fe1dccc7e docs outbound firewall rules 2021-11-30 18:15:49 -05:00
sadnub
1c80f6f3fa Don't allow script arg variable assignment to callable attributes. Fixes #726 2021-11-29 22:18:05 -05:00
sadnub
54d3177fdd also don't include callable attributes with variable substitutions on alert scripts 2021-11-29 22:15:07 -05:00
r3die
a24ad245d2 splashtop 4rd party integration docs and script
splashtop 4rd party integration docs and script
2021-11-29 15:20:29 -08:00
wh1te909
f38cfdcadf fix test script 2021-11-29 18:51:18 +00:00
Dan
92e4ad8ccd Merge pull request #830 from silversword411/develop
docs updates
2021-11-29 09:20:14 -08:00
silversword411
3f3ab088d2 docs - adding bulk delete 2021-11-29 09:47:38 -05:00
sadnub
2c2cbaa175 formatting 2021-11-28 21:08:41 -05:00
sadnub
911b6bf863 fix sorting process cpu percentage. Fixes #831 2021-11-28 21:07:09 -05:00
sadnub
31462cab64 fix tests and also check for the correct script hash 2021-11-28 20:59:21 -05:00
silversword411
1ee35da62d docs updates 2021-11-28 15:31:37 -05:00
sadnub
edf4815595 make script file encoding consistent. utf-8 2021-11-28 13:23:47 -05:00
sadnub
06ccee5d18 add script hash field and calculate hash on script changes. Also removed storing scripts in DB as base64 strings. Should fix #634 2021-11-28 13:23:10 -05:00
sadnub
d5ad85725f fix duplicate package in dev requirements 2021-11-27 23:26:44 -05:00
sadnub
4d5bddb413 rework script form and add syntax field 2021-11-27 22:59:18 -05:00
Dan
2f4da7c381 Merge pull request #829 from ssteeltm/develop
Update unsupported_scripts.md
2021-11-26 16:11:50 -08:00
Dan
8b845fce03 Merge pull request #826 from NiceGuyIT/docs-howitallworks-services
Document server services and configuration
2021-11-26 16:11:24 -08:00
Dan
9fd15c38a9 Merge pull request #825 from silversword411/develop
scripts and docs
2021-11-26 16:11:00 -08:00
silversword411
ec1573d01f Merge branch 'develop' of https://github.com/silversword411/tacticalrmm into develop 2021-11-26 18:05:02 -05:00
silversword411
92ec1cc9e7 docs - add howitallworks to index 2021-11-26 18:05:00 -05:00
Hugo Sampaio
8b2f9665ce Update unsupported_scripts.md
Added info about how I run rmm behind Apache Proxy
( discord Hugo )
2021-11-26 17:25:42 -03:00
silversword411
cb388a5a78 scripts - adding demo server scripts 2021-11-26 14:35:33 -05:00
David Randall
7f4389ae08 Docs: Server services
Document the server services and configuration.
2021-11-25 16:59:19 -05:00
silversword411
76d71beaa2 script_wip addition 2021-11-25 15:06:15 -05:00
silversword411
31bb9c2197 docs - tips and tricks add mesh connection logs 2021-11-25 08:59:21 -05:00
wh1te909
6a2cd5c45a reduce celery memory usage and optimize a query 2021-11-25 06:20:06 +00:00
Dan
520632514b Merge pull request #823 from silversword411/develop
docs - video embed #1 and getting started started
2021-11-24 15:25:46 -08:00
silversword411
f998b28d0b docs - numbering fix 2021-11-24 17:34:43 -05:00
silversword411
1a6587e9e6 Merge branch 'develop' of https://github.com/silversword411/tacticalrmm into develop 2021-11-24 17:13:09 -05:00
silversword411
9b4b729d19 undo vscode spellcheck 2021-11-24 17:12:58 -05:00
silversword411
e80345295e script library - adding security audit 2021-11-24 14:06:44 -05:00
silversword411
026c259a2e added vscode spellcheck, shouldn't go public 2021-11-24 11:32:04 -05:00
silversword411
63474c2269 community scripts - adding syntax to defender enable 2021-11-24 11:30:03 -05:00
silversword411
faa1a9312f scripts - adding parameter check 2021-11-24 11:25:15 -05:00
silversword411
23fa0726d5 docs - v1 of getting started guide 2021-11-24 10:10:46 -05:00
silversword411
22210eaf7d Merge branch 'wh1te909:develop' into develop 2021-11-23 23:40:47 -05:00
silversword411
dcd8bee676 docs - video embed 2021-11-23 23:40:29 -05:00
silversword411
06f0fa8f0e Revert "docs - video embed testing"
This reverts commit 6d0f9e2cd5.
2021-11-23 23:37:54 -05:00
silversword411
6d0f9e2cd5 docs - video embed testing 2021-11-23 23:33:25 -05:00
sadnub
732afdb65d move custom fields to tab in edit agent modal 2021-11-23 21:35:11 -05:00
sadnub
1a9e8742f7 remove the need to type agent name to delete agents in dashboard 2021-11-23 21:35:11 -05:00
sadnub
b8eda37339 Fix setting alert template when policy assignment changes 2021-11-23 21:35:11 -05:00
sadnub
5107db6169 add drf_spectacular to dev requirements 2021-11-23 21:35:11 -05:00
wh1te909
2c8f207454 add mgmt command to bulk delete agents 2021-11-22 20:26:10 +00:00
wh1te909
489bc9c3b3 optimize some queries 2021-11-22 17:24:48 +00:00
wh1te909
514713e883 don't log swagger 2021-11-22 17:23:38 +00:00
wh1te909
17cc0cd09c forgot to check core settings fixes #816 2021-11-22 17:18:58 +00:00
Dan
4475df1295 Merge pull request #815 from tremor021/develop
Update Defender script
2021-11-22 09:06:04 -08:00
Dan
fdad267cfd Merge pull request #814 from silversword411/develop
docs updates
2021-11-22 09:05:19 -08:00
silversword411
3684fc80f0 docs - spellchecking 2021-11-22 08:07:49 -05:00
silversword411
e97a5fef94 script library - adding syntax to tooltip helper 2021-11-22 00:14:19 -05:00
silversword411
de2972631f docs - tips about running scripts syntax 2021-11-21 23:02:47 -05:00
tremor021
e5b8fd67c8 Update Defender script 2021-11-22 02:14:11 +01:00
silversword411
5fade89e2d docs - fixing install and restore docs to eliminate confusion 2021-11-21 15:18:18 -05:00
wh1te909
2eefedadb3 Release 0.10.2 2021-11-21 02:24:29 +00:00
wh1te909
e63d7a0b8a bump version 2021-11-21 02:24:07 +00:00
wh1te909
2a1b1849fa fix nats-api not working in docker 2021-11-21 02:02:29 +00:00
wh1te909
0461cb7f19 update docs 2021-11-20 22:21:02 +00:00
Dan
0932e0be03 Merge pull request #811 from silversword411/develop
docs updates
2021-11-20 14:17:11 -08:00
silversword411
4638ac9474 docs - reiterating no root and backup 2021-11-20 12:59:42 -05:00
silversword411
d8d7255029 docs - filter tips 2021-11-20 12:50:10 -05:00
wh1te909
fa05276c3f black 2021-11-19 20:00:22 +00:00
silversword411
e50a5d51d8 docs - troubleshooting enhancements 2021-11-19 14:14:12 -05:00
sadnub
c03ba78587 make swagger views optional 2021-11-19 13:58:38 -05:00
wh1te909
ff07c69e7d Release 0.10.1 2021-11-19 17:41:12 +00:00
wh1te909
735b84b26d bump version 2021-11-19 17:39:14 +00:00
sadnub
8dd069ad67 push models.py file update for scripts 2021-11-19 12:13:20 -05:00
sadnub
1857e68003 change filename db field to not be required 2021-11-19 10:46:40 -05:00
wh1te909
ff2508382a Release 0.10.0 2021-11-19 08:37:39 +00:00
wh1te909
9cb952b116 bump version 2021-11-19 08:04:25 +00:00
wh1te909
105e8089bb trigger an agent update task after rmm update 2021-11-19 07:25:32 +00:00
wh1te909
730f37f247 add debian 11 support and update reqs 2021-11-19 06:58:18 +00:00
wh1te909
284716751f update docs for new service 2021-11-19 06:32:15 +00:00
sadnub
8d0db699bf remove dynamic agent options function 2021-11-18 21:11:53 -05:00
Dan
53cf1cae58 Merge pull request #807 from silversword411/develop
docs and script adds
2021-11-18 12:32:22 -08:00
silversword411
307e4719e0 wip script - user enable/disabling 2021-11-18 12:23:52 -05:00
silversword411
5effae787a Community scripts - Fixing Drive Volume check 2021-11-18 10:45:25 -05:00
silversword411
6532be0b52 docs - reverting content tabs 2021-11-18 10:05:01 -05:00
silversword411
fb225a5347 community scripts add - Win11 check 2021-11-18 05:24:22 -05:00
silversword411
b83830a45e docs moving position 2021-11-18 05:20:12 -05:00
wh1te909
ca28288c33 add missing onMounted 2021-11-18 08:42:26 +00:00
wh1te909
b6f8d9cb25 change drive color based on percent closes #802 2021-11-18 07:46:17 +00:00
Dan
9cad0f11e5 Merge pull request #803 from silversword411/develop
Scripts and docs
2021-11-17 11:24:29 -08:00
silversword411
807be08566 docs - adding how to invalidate all auth tokens 2021-11-17 10:43:52 -05:00
sadnub
67f6a985f8 increase font size on script editors and fix import error 2021-11-16 21:16:03 -05:00
sadnub
f87d54ae8d move imports for styles and select light or dark theme for editor depending on if dark mode is enabled 2021-11-16 20:45:42 -05:00
sadnub
d894bf7271 move to ace text editor. Fixes script line wrap issue and more features. Fixes #712 2021-11-16 20:19:46 -05:00
sadnub
56e0e5cace formatting 2021-11-15 21:17:28 -05:00
sadnub
685084e784 add agent counts to client/site tooltip. Closes #426 2021-11-15 21:16:18 -05:00
sadnub
cbeec5a973 swagger api documentation start 2021-11-15 17:50:59 -05:00
sadnub
3fff56bcd7 cleanup script manager and snippet modals and move agent select dropdown for test script to script form 2021-11-15 17:50:26 -05:00
silversword411
c504c23eec docs add mesh token recovery 2021-11-15 16:47:18 -05:00
silversword411
16dae5a655 docs Updating index and adding permissions and considerations for choosing install type 2021-11-15 15:42:02 -05:00
silversword411
e512c5ae7d Merge branch 'wh1te909:develop' into develop 2021-11-15 15:39:56 -05:00
silversword411
094078b928 scripts wip adding disk status 2021-11-15 15:26:07 -05:00
wh1te909
34fc3ff919 fix issue where emails/sms were not being sent if recipients in global settings were empty, even if they were present in an alert template recipients 2021-11-15 00:05:42 +00:00
wh1te909
4391f48e78 add some tests 2021-11-14 19:52:21 +00:00
wh1te909
775608a3c0 update reqs 2021-11-14 19:51:28 +00:00
Dan
b326228901 Merge pull request #800 from silversword411/develop
script library - fixing choco
2021-11-14 11:27:40 -08:00
silversword411
b2e98173a8 script library - fixing choco 2021-11-14 13:04:37 -05:00
wh1te909
65c9b7952c have task runs appear in history tab closes #716 2021-11-14 09:18:32 +00:00
wh1te909
b9dc9e7d62 speed up some views 2021-11-14 09:15:43 +00:00
Dan
ce178d0354 Merge pull request #799 from silversword411/develop
Community scripts: Adding syntax for tooltip
2021-11-14 00:54:15 -08:00
sadnub
a3ff6efebc remove nats-api from api dev image 2021-11-13 16:56:50 -05:00
wh1te909
6a9bc56723 update for new service 2021-11-13 21:30:01 +00:00
wh1te909
c9ac158d25 Merge branch 'develop' of https://github.com/wh1te909/tacticalrmm into develop 2021-11-13 20:18:18 +00:00
silversword411
4b937a0fe8 Community scripts: Adding syntax for tooltip 2021-11-13 14:10:05 -05:00
sadnub
405bf26ac5 formatting 2021-11-13 13:40:26 -05:00
sadnub
5dcda0e0a0 allow q-select slots in tactical-dropdown. Fix info icon on run script dialog 2021-11-13 13:39:38 -05:00
sadnub
83e9b60308 when filtering agents add category to the side of options 2021-11-13 12:55:09 -05:00
sadnub
10b40b4730 script syntax highlighting. Resolves #702 2021-11-13 12:55:09 -05:00
wh1te909
79d6d804ef stringify errors before saving to db 2021-11-13 08:31:45 +00:00
wh1te909
e9c7b6d8f8 fix tests 2021-11-13 01:25:25 +00:00
wh1te909
4fcfbfb3f4 more go rework 2021-11-13 00:45:28 +00:00
wh1te909
30cde14ed3 update go mod 2021-11-13 00:36:57 +00:00
wh1te909
cf76e6f538 remove deprecated endpoint, add another deprecation 2021-11-13 00:33:52 +00:00
wh1te909
d0f600ec8d filter_software now handled by agent 2021-11-13 00:32:44 +00:00
wh1te909
675f9e956f remove some celery tasks now handled by agent/go 2021-11-13 00:32:03 +00:00
wh1te909
381605a6bb remove tests 2021-11-13 00:31:06 +00:00
wh1te909
0fce66062b remove some utils now handled by agent 2021-11-13 00:30:45 +00:00
wh1te909
747cc9e5da remove tasks 2021-11-13 00:27:34 +00:00
sadnub
25a1b464da Fix block inheritance on client/site 2021-11-10 22:45:25 -05:00
Dan
3b6738b547 Merge pull request #798 from silversword411/develop
Wip script additions
2021-11-10 11:12:28 -08:00
silversword411
fc93e3e97f Merge branch 'wh1te909:develop' into develop 2021-11-10 11:01:34 -05:00
silversword411
0edbb13d48 scripts wip revert windows update to default settings 2021-11-10 11:00:44 -05:00
silversword411
673687341c scripts wip adding 2021-11-10 09:03:17 -05:00
258 changed files with 24643 additions and 6450 deletions

View File

@@ -1,4 +1,4 @@
FROM python:3.9.6-slim
FROM python:3.9.9-slim
ENV TACTICAL_DIR /opt/tactical
ENV TACTICAL_READY_FILE ${TACTICAL_DIR}/tmp/tactical.ready
@@ -13,10 +13,6 @@ EXPOSE 8000 8383 8005
RUN groupadd -g 1000 tactical && \
useradd -u 1000 -g 1000 tactical
# Copy nats-api file
COPY natsapi/bin/nats-api /usr/local/bin/
RUN chmod +x /usr/local/bin/nats-api
# Copy dev python reqs
COPY .devcontainer/requirements.txt /

View File

@@ -8,7 +8,7 @@ services:
build:
context: ..
dockerfile: .devcontainer/api.dockerfile
command: ["tactical-api"]
command: [ "tactical-api" ]
environment:
API_PORT: ${API_PORT}
ports:
@@ -18,14 +18,15 @@ services:
- ..:/workspace:cached
networks:
dev:
aliases:
aliases:
- tactical-backend
app-dev:
container_name: trmm-app-dev
image: node:14-alpine
restart: always
command: /bin/sh -c "npm install npm@latest -g && npm install && npm run serve -- --host 0.0.0.0 --port ${APP_PORT}"
command: /bin/sh -c "npm install npm@latest -g && npm install && npm run serve
-- --host 0.0.0.0 --port ${APP_PORT}"
working_dir: /workspace/web
volumes:
- ..:/workspace:cached
@@ -33,7 +34,7 @@ services:
- "8080:${APP_PORT}"
networks:
dev:
aliases:
aliases:
- tactical-frontend
# nats
@@ -61,7 +62,7 @@ services:
container_name: trmm-meshcentral-dev
image: ${IMAGE_REPO}tactical-meshcentral:${VERSION}
restart: always
environment:
environment:
MESH_HOST: ${MESH_HOST}
MESH_USER: ${MESH_USER}
MESH_PASS: ${MESH_PASS}
@@ -117,7 +118,7 @@ services:
restart: always
command: redis-server --appendonly yes
image: redis:6.0-alpine
volumes:
volumes:
- redis-data-dev:/data
networks:
dev:
@@ -128,7 +129,7 @@ services:
container_name: trmm-init-dev
image: api-dev
restart: on-failure
command: ["tactical-init-dev"]
command: [ "tactical-init-dev" ]
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASS: ${POSTGRES_PASS}
@@ -153,7 +154,7 @@ services:
celery-dev:
container_name: trmm-celery-dev
image: api-dev
command: ["tactical-celery-dev"]
command: [ "tactical-celery-dev" ]
restart: always
networks:
- dev
@@ -168,7 +169,7 @@ services:
celerybeat-dev:
container_name: trmm-celerybeat-dev
image: api-dev
command: ["tactical-celerybeat-dev"]
command: [ "tactical-celerybeat-dev" ]
restart: always
networks:
- dev
@@ -183,7 +184,7 @@ services:
websockets-dev:
container_name: trmm-websockets-dev
image: api-dev
command: ["tactical-websockets-dev"]
command: [ "tactical-websockets-dev" ]
restart: always
networks:
dev:
@@ -223,7 +224,7 @@ services:
container_name: trmm-mkdocs-dev
image: api-dev
restart: always
command: ["tactical-mkdocs-dev"]
command: [ "tactical-mkdocs-dev" ]
ports:
- "8005:8005"
volumes:
@@ -232,11 +233,11 @@ services:
- dev
volumes:
tactical-data-dev:
postgres-data-dev:
mongo-dev-data:
mesh-data-dev:
redis-data-dev:
tactical-data-dev: null
postgres-data-dev: null
mongo-dev-data: null
mesh-data-dev: null
redis-data-dev: null
networks:
dev:

View File

@@ -9,7 +9,8 @@ set -e
: "${POSTGRES_USER:=tactical}"
: "${POSTGRES_PASS:=tactical}"
: "${POSTGRES_DB:=tacticalrmm}"
: "${MESH_CONTAINER:=tactical-meshcentral}"
: "${MESH_SERVICE:=tactical-meshcentral}"
: "${MESH_WS_URL:=ws://${MESH_SERVICE}:443}"
: "${MESH_USER:=meshcentral}"
: "${MESH_PASS:=meshcentralpass}"
: "${MESH_HOST:=tactical-meshcentral}"
@@ -20,6 +21,9 @@ set -e
: "${APP_PORT:=8080}"
: "${API_PORT:=8000}"
: "${CERT_PRIV_PATH:=${TACTICAL_DIR}/certs/privkey.pem}"
: "${CERT_PUB_PATH:=${TACTICAL_DIR}/certs/fullchain.pem}"
# Add python venv to path
export PATH="${VIRTUAL_ENV}/bin:$PATH"
@@ -37,7 +41,7 @@ function django_setup {
sleep 5
done
until (echo > /dev/tcp/"${MESH_CONTAINER}"/443) &> /dev/null; do
until (echo > /dev/tcp/"${MESH_SERVICE}"/443) &> /dev/null; do
echo "waiting for meshcentral container to be ready..."
sleep 5
done
@@ -56,8 +60,8 @@ DEBUG = True
DOCKER_BUILD = True
CERT_FILE = '/opt/tactical/certs/fullchain.pem'
KEY_FILE = '/opt/tactical/certs/privkey.pem'
CERT_FILE = '${CERT_PUB_PATH}'
KEY_FILE = '${CERT_PRIV_PATH}'
SCRIPTS_DIR = '${WORKSPACE_DIR}/scripts'
@@ -82,6 +86,7 @@ MESH_USERNAME = '${MESH_USER}'
MESH_SITE = 'https://${MESH_HOST}'
MESH_TOKEN_KEY = '${MESH_TOKEN}'
REDIS_HOST = '${REDIS_HOST}'
MESH_WS_URL = '${MESH_WS_URL}'
ADMIN_ENABLED = True
EOF
)"
@@ -96,7 +101,10 @@ EOF
"${VIRTUAL_ENV}"/bin/python manage.py load_chocos
"${VIRTUAL_ENV}"/bin/python manage.py load_community_scripts
"${VIRTUAL_ENV}"/bin/python manage.py reload_nats
"${VIRTUAL_ENV}"/bin/python manage.py create_natsapi_conf
"${VIRTUAL_ENV}"/bin/python manage.py create_installer_user
"${VIRTUAL_ENV}"/bin/python manage.py post_update_tasks
# create super user
echo "from accounts.models import User; User.objects.create_superuser('${TRMM_USER}', 'admin@example.com', '${TRMM_PASS}') if not User.objects.filter(username='${TRMM_USER}').exists() else 0;" | python manage.py shell

View File

@@ -4,7 +4,7 @@ celery
channels
channels_redis
django-ipware
Django
Django==3.2.10
django-cors-headers
django-rest-knox
djangorestframework
@@ -35,3 +35,5 @@ Pygments
mypy
pysnooper
isort
drf_spectacular
pandas

70
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,70 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ develop ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ develop ]
schedule:
- cron: '19 14 * * 6'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'go', 'javascript', 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

34
.github/workflows/devskim-analysis.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
name: DevSkim
on:
push:
branches: [ develop ]
pull_request:
branches: [ develop ]
schedule:
- cron: '19 5 * * 0'
jobs:
lint:
name: DevSkim
runs-on: ubuntu-20.04
permissions:
actions: read
contents: read
security-events: write
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Run DevSkim scanner
uses: microsoft/DevSkim-Action@v1
- name: Upload DevSkim scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v1
with:
sarif_file: devskim-results.sarif

2
.gitignore vendored
View File

@@ -49,3 +49,5 @@ nats-rmm.conf
docs/site/
reset_db.sh
run_go_cmd.py
nats-api.conf

19
SECURITY.md Normal file
View File

@@ -0,0 +1,19 @@
# Security Policy
## Supported Versions
Use this section to tell people about which versions of your project are
currently being supported with security updates.
| Version | Supported |
| ------- | ------------------ |
| 0.10.4 | :white_check_mark: |
| < 0.10.4| :x: |
## Reporting a Vulnerability
Use this section to tell people how to report a vulnerability.
Tell them where to go, how often they can expect to get an update on a
reported vulnerability, what to expect if the vulnerability is accepted or
declined, etc.

View File

@@ -21,4 +21,6 @@ omit =
*/tests.py
*/test.py
checks/utils.py
*/asgi.py
*/demo_views.py

View File

@@ -6,7 +6,6 @@ from django.shortcuts import get_object_or_404
from ipware import get_client_ip
from knox.views import LoginView as KnoxLoginView
from logs.models import AuditLog
from rest_framework import status
from rest_framework.authtoken.serializers import AuthTokenSerializer
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
@@ -25,11 +24,15 @@ from .serializers import (
def _is_root_user(request, user) -> bool:
return (
root = (
hasattr(settings, "ROOT_USER")
and request.user != user
and user.username == settings.ROOT_USER
)
demo = (
getattr(settings, "DEMO", False) and request.user.username == settings.ROOT_USER
)
return root or demo
class CheckCreds(KnoxLoginView):
@@ -80,6 +83,8 @@ class LoginView(KnoxLoginView):
if settings.DEBUG and token == "sekret":
valid = True
elif getattr(settings, "DEMO", False):
valid = True
elif totp.verify(token, valid_window=10):
valid = True

View File

@@ -0,0 +1,81 @@
import asyncio
from django.core.management.base import BaseCommand
from django.utils import timezone as djangotime
from packaging import version as pyver
from agents.models import Agent
from tacticalrmm.utils import AGENT_DEFER, reload_nats
class Command(BaseCommand):
help = "Delete old agents"
def add_arguments(self, parser):
parser.add_argument(
"--days",
type=int,
help="Delete agents that have not checked in for this many days",
)
parser.add_argument(
"--agentver",
type=str,
help="Delete agents that equal to or less than this version",
)
parser.add_argument(
"--delete",
action="store_true",
help="This will delete agents",
)
def handle(self, *args, **kwargs):
days = kwargs["days"]
agentver = kwargs["agentver"]
delete = kwargs["delete"]
if not days and not agentver:
self.stdout.write(
self.style.ERROR("Must have at least one parameter: days or agentver")
)
return
q = Agent.objects.defer(*AGENT_DEFER)
agents = []
if days:
overdue = djangotime.now() - djangotime.timedelta(days=days)
agents = [i for i in q if i.last_seen < overdue]
if agentver:
agents = [i for i in q if pyver.parse(i.version) <= pyver.parse(agentver)]
if not agents:
self.stdout.write(self.style.ERROR("No agents matched"))
return
deleted_count = 0
for agent in agents:
s = f"{agent.hostname} | Version {agent.version} | Last Seen {agent.last_seen} | {agent.client} > {agent.site}"
if delete:
s = "Deleting " + s
self.stdout.write(self.style.SUCCESS(s))
asyncio.run(agent.nats_cmd({"func": "uninstall"}, wait=False))
try:
agent.delete()
except Exception as e:
err = f"Failed to delete agent {agent.hostname}: {str(e)}"
self.stdout.write(self.style.ERROR(err))
else:
deleted_count += 1
else:
self.stdout.write(self.style.WARNING(s))
if delete:
reload_nats()
self.stdout.write(self.style.SUCCESS(f"Deleted {deleted_count} agents"))
else:
self.stdout.write(
self.style.SUCCESS(
"The above agents would be deleted. Run again with --delete to actually delete them."
)
)

View File

@@ -0,0 +1,36 @@
# import datetime as dt
import random
from django.core.management.base import BaseCommand
from django.utils import timezone as djangotime
from agents.models import Agent
class Command(BaseCommand):
help = "stuff for demo site in cron"
def handle(self, *args, **kwargs):
random_dates = []
now = djangotime.now()
for _ in range(20):
rand = now - djangotime.timedelta(minutes=random.randint(1, 2))
random_dates.append(rand)
for _ in range(5):
rand = now - djangotime.timedelta(minutes=random.randint(10, 20))
random_dates.append(rand)
""" for _ in range(5):
rand = djangotime.now() - djangotime.timedelta(hours=random.randint(1, 10))
random_dates.append(rand)
for _ in range(5):
rand = djangotime.now() - djangotime.timedelta(days=random.randint(40, 90))
random_dates.append(rand) """
agents = Agent.objects.only("last_seen")
for agent in agents:
agent.last_seen = random.choice(random_dates)
agent.save(update_fields=["last_seen"])

View File

@@ -0,0 +1,668 @@
import json
import random
import string
import datetime as dt
from django.core.management.base import BaseCommand
from django.utils import timezone as djangotime
from django.conf import settings
from accounts.models import User
from agents.models import Agent, AgentHistory
from clients.models import Client, Site
from software.models import InstalledSoftware
from winupdate.models import WinUpdate, WinUpdatePolicy
from checks.models import Check, CheckHistory
from scripts.models import Script
from autotasks.models import AutomatedTask
from automation.models import Policy
from logs.models import PendingAction, AuditLog
from tacticalrmm.demo_data import (
disks,
temp_dir_stdout,
spooler_stdout,
ping_fail_output,
ping_success_output,
)
AGENTS_TO_GENERATE = 250
SVCS = settings.BASE_DIR.joinpath("tacticalrmm/test_data/winsvcs.json")
WMI_1 = settings.BASE_DIR.joinpath("tacticalrmm/test_data/wmi1.json")
WMI_2 = settings.BASE_DIR.joinpath("tacticalrmm/test_data/wmi2.json")
WMI_3 = settings.BASE_DIR.joinpath("tacticalrmm/test_data/wmi3.json")
SW_1 = settings.BASE_DIR.joinpath("tacticalrmm/test_data/software1.json")
SW_2 = settings.BASE_DIR.joinpath("tacticalrmm/test_data/software2.json")
WIN_UPDATES = settings.BASE_DIR.joinpath("tacticalrmm/test_data/winupdates.json")
EVT_LOG_FAIL = settings.BASE_DIR.joinpath(
"tacticalrmm/test_data/eventlog_check_fail.json"
)
class Command(BaseCommand):
help = "populate database with fake agents"
def rand_string(self, length):
chars = string.ascii_letters
return "".join(random.choice(chars) for _ in range(length))
def handle(self, *args, **kwargs):
user = User.objects.first()
user.totp_key = "ABSA234234"
user.save(update_fields=["totp_key"])
Client.objects.all().delete()
Agent.objects.all().delete()
Check.objects.all().delete()
Script.objects.all().delete()
AutomatedTask.objects.all().delete()
CheckHistory.objects.all().delete()
Policy.objects.all().delete()
AuditLog.objects.all().delete()
PendingAction.objects.all().delete()
Script.load_community_scripts()
# policies
check_policy = Policy()
check_policy.name = "Demo Checks Policy"
check_policy.desc = "Demo Checks Policy"
check_policy.active = True
check_policy.enforced = True
check_policy.save()
patch_policy = Policy()
patch_policy.name = "Demo Patch Policy"
patch_policy.desc = "Demo Patch Policy"
patch_policy.active = True
patch_policy.enforced = True
patch_policy.save()
update_policy = WinUpdatePolicy()
update_policy.policy = patch_policy
update_policy.critical = "approve"
update_policy.important = "approve"
update_policy.moderate = "approve"
update_policy.low = "ignore"
update_policy.other = "ignore"
update_policy.run_time_days = [6, 0, 2]
update_policy.run_time_day = 1
update_policy.reboot_after_install = "required"
update_policy.reprocess_failed = True
update_policy.email_if_fail = True
update_policy.save()
clients = [
"Company 2",
"Company 3",
"Company 1",
"Company 4",
"Company 5",
"Company 6",
]
sites1 = ["HQ1", "LA Office 1", "NY Office 1"]
sites2 = ["HQ2", "LA Office 2", "NY Office 2"]
sites3 = ["HQ3", "LA Office 3", "NY Office 3"]
sites4 = ["HQ4", "LA Office 4", "NY Office 4"]
sites5 = ["HQ5", "LA Office 5", "NY Office 5"]
sites6 = ["HQ6", "LA Office 6", "NY Office 6"]
client1 = Client(name="Company 1")
client2 = Client(name="Company 2")
client3 = Client(name="Company 3")
client4 = Client(name="Company 4")
client5 = Client(name="Company 5")
client6 = Client(name="Company 6")
client1.save()
client2.save()
client3.save()
client4.save()
client5.save()
client6.save()
for site in sites1:
Site(client=client1, name=site).save()
for site in sites2:
Site(client=client2, name=site).save()
for site in sites3:
Site(client=client3, name=site).save()
for site in sites4:
Site(client=client4, name=site).save()
for site in sites5:
Site(client=client5, name=site).save()
for site in sites6:
Site(client=client6, name=site).save()
hostnames = [
"DC-1",
"DC-2",
"FSV-1",
"FSV-2",
"WSUS",
"DESKTOP-12345",
"LAPTOP-55443",
]
descriptions = ["Bob's computer", "Primary DC", "File Server", "Karen's Laptop"]
modes = ["server", "workstation"]
op_systems_servers = [
"Microsoft Windows Server 2016 Standard, 64bit (build 14393)",
"Microsoft Windows Server 2012 R2 Standard, 64bit (build 9600)",
"Microsoft Windows Server 2019 Standard, 64bit (build 17763)",
]
op_systems_workstations = [
"Microsoft Windows 8.1 Pro, 64bit (build 9600)",
"Microsoft Windows 10 Pro for Workstations, 64bit (build 18363)",
"Microsoft Windows 10 Pro, 64bit (build 18363)",
]
public_ips = ["65.234.22.4", "74.123.43.5", "44.21.134.45"]
total_rams = [4, 8, 16, 32, 64, 128]
used_rams = [10, 13, 60, 25, 76, 34, 56, 34, 39]
now = dt.datetime.now()
boot_times = []
for _ in range(15):
rand_hour = now - dt.timedelta(hours=random.randint(1, 22))
boot_times.append(str(rand_hour.timestamp()))
for _ in range(5):
rand_days = now - dt.timedelta(days=random.randint(2, 50))
boot_times.append(str(rand_days.timestamp()))
user_names = ["None", "Karen", "Steve", "jsmith", "jdoe"]
with open(SVCS) as f:
services = json.load(f)
# WMI
with open(WMI_1) as f:
wmi1 = json.load(f)
with open(WMI_2) as f:
wmi2 = json.load(f)
with open(WMI_3) as f:
wmi3 = json.load(f)
wmi_details = []
wmi_details.append(wmi1)
wmi_details.append(wmi2)
wmi_details.append(wmi3)
# software
with open(SW_1) as f:
software1 = json.load(f)
with open(SW_2) as f:
software2 = json.load(f)
softwares = []
softwares.append(software1)
softwares.append(software2)
# windows updates
with open(WIN_UPDATES) as f:
windows_updates = json.load(f)["samplecomputer"]
# event log check fail data
with open(EVT_LOG_FAIL) as f:
eventlog_check_fail_data = json.load(f)
# create scripts
clear_spool = Script()
clear_spool.name = "Clear Print Spooler"
clear_spool.description = "clears the print spooler. Fuck printers"
clear_spool.filename = "clear_print_spool.bat"
clear_spool.shell = "cmd"
clear_spool.save()
check_net_aware = Script()
check_net_aware.name = "Check Network Location Awareness"
check_net_aware.description = "Check's network location awareness on domain computers, should always be domain profile and not public or private. Sometimes happens when computer restarts before domain available. This script will return 0 if check passes or 1 if it fails."
check_net_aware.filename = "check_network_loc_aware.ps1"
check_net_aware.shell = "powershell"
check_net_aware.save()
check_pool_health = Script()
check_pool_health.name = "Check storage spool health"
check_pool_health.description = "loops through all storage pools and will fail if any of them are not healthy"
check_pool_health.filename = "check_storage_pool_health.ps1"
check_pool_health.shell = "powershell"
check_pool_health.save()
restart_nla = Script()
restart_nla.name = "Restart NLA Service"
restart_nla.description = "restarts the Network Location Awareness windows service to fix the nic profile. Run this after the check network service fails"
restart_nla.filename = "restart_nla.ps1"
restart_nla.shell = "powershell"
restart_nla.save()
show_tmp_dir_script = Script()
show_tmp_dir_script.name = "Check temp dir"
show_tmp_dir_script.description = "shows files in temp dir using python"
show_tmp_dir_script.filename = "show_temp_dir.py"
show_tmp_dir_script.shell = "python"
show_tmp_dir_script.save()
for count_agents in range(AGENTS_TO_GENERATE):
client = random.choice(clients)
if client == "Company 1":
site = random.choice(sites1)
elif client == "Company 2":
site = random.choice(sites2)
elif client == "Company 3":
site = random.choice(sites3)
elif client == "Company 4":
site = random.choice(sites4)
elif client == "Company 5":
site = random.choice(sites5)
elif client == "Company 6":
site = random.choice(sites6)
agent = Agent()
mode = random.choice(modes)
if mode == "server":
agent.operating_system = random.choice(op_systems_servers)
else:
agent.operating_system = random.choice(op_systems_workstations)
agent.hostname = random.choice(hostnames)
agent.version = settings.LATEST_AGENT_VER
agent.salt_ver = "1.1.0"
agent.site = Site.objects.get(name=site)
agent.agent_id = self.rand_string(25)
agent.description = random.choice(descriptions)
agent.monitoring_type = mode
agent.public_ip = random.choice(public_ips)
agent.last_seen = djangotime.now()
agent.plat = "windows"
agent.plat_release = "windows-2019Server"
agent.total_ram = random.choice(total_rams)
agent.used_ram = random.choice(used_rams)
agent.boot_time = random.choice(boot_times)
agent.logged_in_username = random.choice(user_names)
agent.antivirus = "windowsdefender"
agent.mesh_node_id = (
"3UiLhe420@kaVQ0rswzBeonW$WY0xrFFUDBQlcYdXoriLXzvPmBpMrV99vRHXFlb"
)
agent.overdue_email_alert = random.choice([True, False])
agent.overdue_text_alert = random.choice([True, False])
agent.needs_reboot = random.choice([True, False])
agent.wmi_detail = random.choice(wmi_details)
agent.services = services
agent.disks = random.choice(disks)
agent.salt_id = "not-used"
agent.save()
InstalledSoftware(agent=agent, software=random.choice(softwares)).save()
if mode == "workstation":
WinUpdatePolicy(agent=agent, run_time_days=[5, 6]).save()
else:
WinUpdatePolicy(agent=agent).save()
# windows updates load
guids = []
for k in windows_updates.keys():
guids.append(k)
for i in guids:
WinUpdate(
agent=agent,
guid=i,
kb=windows_updates[i]["KBs"][0],
mandatory=windows_updates[i]["Mandatory"],
title=windows_updates[i]["Title"],
needs_reboot=windows_updates[i]["NeedsReboot"],
installed=windows_updates[i]["Installed"],
downloaded=windows_updates[i]["Downloaded"],
description=windows_updates[i]["Description"],
severity=windows_updates[i]["Severity"],
).save()
# agent histories
hist = AgentHistory()
hist.agent = agent
hist.type = "cmd_run"
hist.command = "ping google.com"
hist.username = "demo"
hist.results = ping_success_output
hist.save()
hist1 = AgentHistory()
hist1.agent = agent
hist1.type = "script_run"
hist1.script = clear_spool
hist1.script_results = {
"id": 1,
"stderr": "",
"stdout": spooler_stdout,
"execution_time": 3.5554593,
"retcode": 0,
}
hist1.save()
# disk space check
check1 = Check()
check1.agent = agent
check1.check_type = "diskspace"
check1.status = "passing"
check1.last_run = djangotime.now()
check1.more_info = "Total: 498.7GB, Free: 287.4GB"
check1.warning_threshold = 25
check1.error_threshold = 10
check1.disk = "C:"
check1.email_alert = random.choice([True, False])
check1.text_alert = random.choice([True, False])
check1.save()
for i in range(30):
check1_history = CheckHistory()
check1_history.check_id = check1.id
check1_history.x = djangotime.now() - djangotime.timedelta(
minutes=i * 2
)
check1_history.y = random.randint(13, 40)
check1_history.save()
# ping check
check2 = Check()
check2.agent = agent
check2.check_type = "ping"
check2.last_run = djangotime.now()
check2.email_alert = random.choice([True, False])
check2.text_alert = random.choice([True, False])
if site in sites5:
check2.name = "Synology NAS"
check2.status = "failing"
check2.ip = "172.17.14.26"
check2.more_info = ping_fail_output
else:
check2.name = "Google"
check2.status = "passing"
check2.ip = "8.8.8.8"
check2.more_info = ping_success_output
check2.save()
for i in range(30):
check2_history = CheckHistory()
check2_history.check_id = check2.id
check2_history.x = djangotime.now() - djangotime.timedelta(
minutes=i * 2
)
if site in sites5:
check2_history.y = 1
check2_history.results = ping_fail_output
else:
check2_history.y = 0
check2_history.results = ping_success_output
check2_history.save()
# cpu load check
check3 = Check()
check3.agent = agent
check3.check_type = "cpuload"
check3.status = "passing"
check3.last_run = djangotime.now()
check3.warning_threshold = 70
check3.error_threshold = 90
check3.history = [15, 23, 16, 22, 22, 27, 15, 23, 23, 20, 10, 10, 13, 34]
check3.email_alert = random.choice([True, False])
check3.text_alert = random.choice([True, False])
check3.save()
for i in range(30):
check3_history = CheckHistory()
check3_history.check_id = check3.id
check3_history.x = djangotime.now() - djangotime.timedelta(
minutes=i * 2
)
check3_history.y = random.randint(2, 79)
check3_history.save()
# memory check
check4 = Check()
check4.agent = agent
check4.check_type = "memory"
check4.status = "passing"
check4.warning_threshold = 70
check4.error_threshold = 85
check4.history = [34, 34, 35, 36, 34, 34, 34, 34, 34, 34]
check4.email_alert = random.choice([True, False])
check4.text_alert = random.choice([True, False])
check4.save()
for i in range(30):
check4_history = CheckHistory()
check4_history.check_id = check4.id
check4_history.x = djangotime.now() - djangotime.timedelta(
minutes=i * 2
)
check4_history.y = random.randint(2, 79)
check4_history.save()
# script check storage pool
check5 = Check()
check5.agent = agent
check5.check_type = "script"
check5.status = "passing"
check5.last_run = djangotime.now()
check5.email_alert = random.choice([True, False])
check5.text_alert = random.choice([True, False])
check5.timeout = 120
check5.retcode = 0
check5.execution_time = "4.0000"
check5.script = check_pool_health
check5.save()
for i in range(30):
check5_history = CheckHistory()
check5_history.check_id = check5.id
check5_history.x = djangotime.now() - djangotime.timedelta(
minutes=i * 2
)
if i == 10 or i == 18:
check5_history.y = 1
else:
check5_history.y = 0
check5_history.save()
check6 = Check()
check6.agent = agent
check6.check_type = "script"
check6.status = "passing"
check6.last_run = djangotime.now()
check6.email_alert = random.choice([True, False])
check6.text_alert = random.choice([True, False])
check6.timeout = 120
check6.retcode = 0
check6.execution_time = "4.0000"
check6.script = check_net_aware
check6.save()
for i in range(30):
check6_history = CheckHistory()
check6_history.check_id = check6.id
check6_history.x = djangotime.now() - djangotime.timedelta(
minutes=i * 2
)
check6_history.y = 0
check6_history.save()
nla_task = AutomatedTask()
nla_task.agent = agent
nla_task.script = restart_nla
nla_task.assigned_check = check6
nla_task.name = "Restart NLA"
nla_task.task_type = "checkfailure"
nla_task.win_task_name = "demotask123"
nla_task.execution_time = "1.8443"
nla_task.last_run = djangotime.now()
nla_task.stdout = "no stdout"
nla_task.retcode = 0
nla_task.sync_status = "synced"
nla_task.save()
spool_task = AutomatedTask()
spool_task.agent = agent
spool_task.script = clear_spool
spool_task.name = "Clear the print spooler"
spool_task.task_type = "scheduled"
spool_task.run_time_bit_weekdays = 127
spool_task.run_time_minute = "04:45"
spool_task.win_task_name = "demospool123"
spool_task.last_run = djangotime.now()
spool_task.retcode = 0
spool_task.stdout = spooler_stdout
spool_task.sync_status = "synced"
spool_task.save()
tmp_dir_task = AutomatedTask()
tmp_dir_task.agent = agent
tmp_dir_task.name = "show temp dir files"
tmp_dir_task.script = show_tmp_dir_script
tmp_dir_task.task_type = "manual"
tmp_dir_task.win_task_name = "demotemp"
tmp_dir_task.last_run = djangotime.now()
tmp_dir_task.stdout = temp_dir_stdout
tmp_dir_task.retcode = 0
tmp_dir_task.sync_status = "synced"
tmp_dir_task.save()
check7 = Check()
check7.agent = agent
check7.check_type = "script"
check7.status = "passing"
check7.last_run = djangotime.now()
check7.email_alert = random.choice([True, False])
check7.text_alert = random.choice([True, False])
check7.timeout = 120
check7.retcode = 0
check7.execution_time = "3.1337"
check7.script = clear_spool
check7.stdout = spooler_stdout
check7.save()
for i in range(30):
check7_history = CheckHistory()
check7_history.check_id = check7.id
check7_history.x = djangotime.now() - djangotime.timedelta(
minutes=i * 2
)
check7_history.y = 0
check7_history.save()
check8 = Check()
check8.agent = agent
check8.check_type = "winsvc"
check8.status = "passing"
check8.last_run = djangotime.now()
check8.email_alert = random.choice([True, False])
check8.text_alert = random.choice([True, False])
check8.more_info = "Status RUNNING"
check8.fails_b4_alert = 4
check8.svc_name = "Spooler"
check8.svc_display_name = "Print Spooler"
check8.pass_if_start_pending = False
check8.restart_if_stopped = True
check8.save()
for i in range(30):
check8_history = CheckHistory()
check8_history.check_id = check8.id
check8_history.x = djangotime.now() - djangotime.timedelta(
minutes=i * 2
)
if i == 10 or i == 18:
check8_history.y = 1
check8_history.results = "Status STOPPED"
else:
check8_history.y = 0
check8_history.results = "Status RUNNING"
check8_history.save()
check9 = Check()
check9.agent = agent
check9.check_type = "eventlog"
check9.name = "unexpected shutdown"
check9.last_run = djangotime.now()
check9.email_alert = random.choice([True, False])
check9.text_alert = random.choice([True, False])
check9.fails_b4_alert = 2
if site in sites5:
check9.extra_details = eventlog_check_fail_data
check9.status = "failing"
else:
check9.extra_details = {"log": []}
check9.status = "passing"
check9.log_name = "Application"
check9.event_id = 1001
check9.event_type = "INFO"
check9.fail_when = "contains"
check9.search_last_days = 30
check9.save()
for i in range(30):
check9_history = CheckHistory()
check9_history.check_id = check9.id
check9_history.x = djangotime.now() - djangotime.timedelta(
minutes=i * 2
)
if i == 10 or i == 18:
check9_history.y = 1
check9_history.results = "Events Found: 16"
else:
check9_history.y = 0
check9_history.results = "Events Found: 0"
check9_history.save()
pick = random.randint(1, 10)
if pick == 5 or pick == 3:
reboot_time = djangotime.now() + djangotime.timedelta(
minutes=random.randint(1000, 500000)
)
date_obj = dt.datetime.strftime(reboot_time, "%Y-%m-%d %H:%M")
obj = dt.datetime.strptime(date_obj, "%Y-%m-%d %H:%M")
task_name = "TacticalRMM_SchedReboot_" + "".join(
random.choice(string.ascii_letters) for _ in range(10)
)
sched_reboot = PendingAction()
sched_reboot.agent = agent
sched_reboot.action_type = "schedreboot"
sched_reboot.details = {
"time": str(obj),
"taskname": task_name,
}
sched_reboot.save()
self.stdout.write(self.style.SUCCESS(f"Added agent # {count_agents + 1}"))
self.stdout.write("done")

View File

@@ -0,0 +1,25 @@
from django.conf import settings
from django.core.management.base import BaseCommand
from packaging import version as pyver
from agents.models import Agent
from core.models import CoreSettings
from agents.tasks import send_agent_update_task
from tacticalrmm.utils import AGENT_DEFER
class Command(BaseCommand):
help = "Triggers an agent update task to run"
def handle(self, *args, **kwargs):
core = CoreSettings.objects.first()
if not core.agent_auto_update: # type: ignore
return
q = Agent.objects.defer(*AGENT_DEFER).exclude(version=settings.LATEST_AGENT_VER)
agent_ids: list[str] = [
i.agent_id
for i in q
if pyver.parse(i.version) < pyver.parse(settings.LATEST_AGENT_VER)
]
send_agent_update_task.delay(agent_ids=agent_ids)

View File

@@ -18,7 +18,6 @@ from django.db import models
from django.utils import timezone as djangotime
from nats.aio.client import Client as NATS
from nats.aio.errors import ErrTimeout
from packaging import version as pyver
from core.models import TZ_CHOICES, CoreSettings
from logs.models import BaseAuditModel, DebugLog
@@ -98,7 +97,7 @@ class Agent(BaseAuditModel):
# check if new agent has been created
# or check if policy have changed on agent
# or if site has changed on agent and if so generate-policies
# or if site has changed on agent and if so generate policies
# or if agent was changed from server or workstation
if (
not old_agent
@@ -109,10 +108,6 @@ class Agent(BaseAuditModel):
):
generate_agent_checks_task.delay(agents=[self.pk], create_tasks=True)
# calculate alert template for new agents
if not old_agent:
self.set_alert_template()
def __str__(self):
return self.hostname
@@ -349,7 +344,7 @@ class Agent(BaseAuditModel):
},
}
if history_pk != 0 and pyver.parse(self.version) >= pyver.parse("1.6.0"):
if history_pk != 0:
data["id"] = history_pk
running_agent = self
@@ -748,8 +743,8 @@ class Agent(BaseAuditModel):
try:
ret = msgpack.loads(msg.data) # type: ignore
except Exception as e:
DebugLog.error(agent=self, log_type="agent_issues", message=e)
ret = str(e)
DebugLog.error(agent=self, log_type="agent_issues", message=ret)
await nc.close()
return ret
@@ -930,7 +925,7 @@ class AgentCustomField(models.Model):
)
def __str__(self):
return self.field
return self.field.name
@property
def value(self):

View File

@@ -38,13 +38,15 @@ class AgentSerializer(serializers.ModelSerializer):
client = serializers.ReadOnlyField(source="client.name")
site_name = serializers.ReadOnlyField(source="site.name")
custom_fields = AgentCustomFieldSerializer(many=True, read_only=True)
patches_last_installed = serializers.ReadOnlyField()
last_seen = serializers.ReadOnlyField()
def get_all_timezones(self, obj):
return pytz.all_timezones
class Meta:
model = Agent
exclude = ["last_seen", "id", "patches_last_installed"]
exclude = ["id"]
class AgentTableSerializer(serializers.ModelSerializer):

View File

@@ -4,7 +4,6 @@ import random
from time import sleep
from typing import Union
from alerts.models import Alert
from core.models import CoreSettings
from django.conf import settings
from django.utils import timezone as djangotime
@@ -12,7 +11,6 @@ from logs.models import DebugLog, PendingAction
from packaging import version as pyver
from scripts.models import Script
from tacticalrmm.celery import app
from tacticalrmm.utils import run_nats_api_cmd
from agents.models import Agent
from agents.utils import get_winagent_url
@@ -80,7 +78,7 @@ def force_code_sign(agent_ids: list[str]) -> None:
@app.task
def send_agent_update_task(agent_ids: list[str]) -> None:
chunks = (agent_ids[i : i + 30] for i in range(0, len(agent_ids), 30))
chunks = (agent_ids[i : i + 50] for i in range(0, len(agent_ids), 50))
for chunk in chunks:
for agent_id in chunk:
agent_update(agent_id)
@@ -268,7 +266,7 @@ def run_script_email_results_task(
server.send_message(msg)
server.quit()
except Exception as e:
DebugLog.error(message=e)
DebugLog.error(message=str(e))
@app.task
@@ -299,25 +297,6 @@ def clear_faults_task(older_than_days: int) -> None:
)
@app.task
def get_wmi_task() -> None:
agents = Agent.objects.only(
"pk", "agent_id", "last_seen", "overdue_time", "offline_time"
)
ids = [i.agent_id for i in agents if i.status == "online"]
run_nats_api_cmd("wmi", ids, timeout=45)
@app.task
def agent_checkin_task() -> None:
run_nats_api_cmd("checkin", timeout=30)
@app.task
def agent_getinfo_task() -> None:
run_nats_api_cmd("agentinfo", timeout=30)
@app.task
def prune_agent_history(older_than_days: int) -> str:
from .models import AgentHistory
@@ -327,45 +306,3 @@ def prune_agent_history(older_than_days: int) -> str:
).delete()
return "ok"
@app.task
def handle_agents_task() -> None:
q = Agent.objects.prefetch_related("pendingactions", "autotasks").only(
"pk", "agent_id", "version", "last_seen", "overdue_time", "offline_time"
)
agents = [
i
for i in q
if pyver.parse(i.version) >= pyver.parse("1.6.0") and i.status == "online"
]
for agent in agents:
# change agent update pending status to completed if agent has just updated
if (
pyver.parse(agent.version) == pyver.parse(settings.LATEST_AGENT_VER)
and agent.pendingactions.filter(
action_type="agentupdate", status="pending"
).exists()
):
agent.pendingactions.filter(
action_type="agentupdate", status="pending"
).update(status="completed")
# sync scheduled tasks
if agent.autotasks.exclude(sync_status="synced").exists(): # type: ignore
tasks = agent.autotasks.exclude(sync_status="synced") # type: ignore
for task in tasks:
if task.sync_status == "pendingdeletion":
task.delete_task_on_agent()
elif task.sync_status == "initial":
task.modify_task_on_agent()
elif task.sync_status == "notsynced":
task.create_task_on_agent()
# handles any alerting actions
if Alert.objects.filter(agent=agent, resolved=False).exists():
try:
Alert.handle_alert_resolve(agent)
except:
continue

View File

@@ -306,8 +306,8 @@ class TestAgentViews(TacticalTestCase):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
assert any(i["name"] == "Registry" for i in mock_ret.return_value)
assert any(i["membytes"] == 434655234324 for i in mock_ret.return_value)
assert any(i["name"] == "spoolsv.exe" for i in mock_ret.return_value)
assert any(i["membytes"] == 17305600 for i in mock_ret.return_value)
mock_ret.return_value = "timeout"
r = self.client.get(url)
@@ -626,7 +626,7 @@ class TestAgentViews(TacticalTestCase):
@patch("agents.tasks.run_script_email_results_task.delay")
@patch("agents.models.Agent.run_script")
def test_run_script(self, run_script, email_task):
from .models import AgentCustomField, Note
from .models import AgentCustomField, Note, AgentHistory
from clients.models import ClientCustomField, SiteCustomField
run_script.return_value = "ok"
@@ -643,8 +643,9 @@ class TestAgentViews(TacticalTestCase):
r = self.client.post(url, data, format="json")
self.assertEqual(r.status_code, 200)
hist = AgentHistory.objects.filter(agent=self.agent, script=script).last()
run_script.assert_called_with(
scriptpk=script.pk, args=[], timeout=18, wait=True, history_pk=0
scriptpk=script.pk, args=[], timeout=18, wait=True, history_pk=hist.pk
)
run_script.reset_mock()
@@ -690,8 +691,9 @@ class TestAgentViews(TacticalTestCase):
r = self.client.post(url, data, format="json")
self.assertEqual(r.status_code, 200)
hist = AgentHistory.objects.filter(agent=self.agent, script=script).last()
run_script.assert_called_with(
scriptpk=script.pk, args=["hello", "world"], timeout=25, history_pk=0
scriptpk=script.pk, args=["hello", "world"], timeout=25, history_pk=hist.pk
)
run_script.reset_mock()
@@ -710,12 +712,13 @@ class TestAgentViews(TacticalTestCase):
r = self.client.post(url, data, format="json")
self.assertEqual(r.status_code, 200)
hist = AgentHistory.objects.filter(agent=self.agent, script=script).last()
run_script.assert_called_with(
scriptpk=script.pk,
args=["hello", "world"],
timeout=25,
wait=True,
history_pk=0,
history_pk=hist.pk,
)
run_script.reset_mock()
@@ -737,12 +740,13 @@ class TestAgentViews(TacticalTestCase):
r = self.client.post(url, data, format="json")
self.assertEqual(r.status_code, 200)
hist = AgentHistory.objects.filter(agent=self.agent, script=script).last()
run_script.assert_called_with(
scriptpk=script.pk,
args=["hello", "world"],
timeout=25,
wait=True,
history_pk=0,
history_pk=hist.pk,
)
run_script.reset_mock()
@@ -766,12 +770,13 @@ class TestAgentViews(TacticalTestCase):
r = self.client.post(url, data, format="json")
self.assertEqual(r.status_code, 200)
hist = AgentHistory.objects.filter(agent=self.agent, script=script).last()
run_script.assert_called_with(
scriptpk=script.pk,
args=["hello", "world"],
timeout=25,
wait=True,
history_pk=0,
history_pk=hist.pk,
)
run_script.reset_mock()
@@ -792,12 +797,13 @@ class TestAgentViews(TacticalTestCase):
r = self.client.post(url, data, format="json")
self.assertEqual(r.status_code, 200)
hist = AgentHistory.objects.filter(agent=self.agent, script=script).last()
run_script.assert_called_with(
scriptpk=script.pk,
args=["hello", "world"],
timeout=25,
wait=True,
history_pk=0,
history_pk=hist.pk,
)
run_script.reset_mock()

View File

@@ -20,7 +20,12 @@ from core.models import CoreSettings
from logs.models import AuditLog, DebugLog, PendingAction
from scripts.models import Script
from scripts.tasks import handle_bulk_command_task, handle_bulk_script_task
from tacticalrmm.utils import get_default_timezone, notify_error, reload_nats
from tacticalrmm.utils import (
get_default_timezone,
notify_error,
reload_nats,
AGENT_DEFER,
)
from winupdate.serializers import WinUpdatePolicySerializer
from winupdate.tasks import bulk_check_for_updates_task, bulk_install_updates_task
from tacticalrmm.permissions import (
@@ -74,34 +79,13 @@ class GetAgents(APIView):
or "detail" in request.query_params.keys()
and request.query_params["detail"] == "true"
):
agents = (
Agent.objects.filter_by_role(request.user)
Agent.objects.filter_by_role(request.user) # type: ignore
.select_related("site", "policy", "alert_template")
.prefetch_related("agentchecks")
.filter(filter)
.only(
"pk",
"hostname",
"agent_id",
"site",
"policy",
"alert_template",
"monitoring_type",
"description",
"needs_reboot",
"overdue_text_alert",
"overdue_email_alert",
"overdue_time",
"offline_time",
"last_seen",
"boot_time",
"logged_in_username",
"last_logged_in_user",
"time_zone",
"maintenance_mode",
"pending_actions_count",
"has_patches_pending",
)
.defer(*AGENT_DEFER)
)
ctx = {"default_tz": get_default_timezone()}
serializer = AgentTableSerializer(agents, many=True, context=ctx)
@@ -109,7 +93,7 @@ class GetAgents(APIView):
# if detail=false
else:
agents = (
Agent.objects.filter_by_role(request.user)
Agent.objects.filter_by_role(request.user) # type: ignore
.select_related("site")
.filter(filter)
.only("agent_id", "hostname", "site")
@@ -125,9 +109,7 @@ class GetUpdateDeleteAgent(APIView):
# get agent details
def get(self, request, agent_id):
agent = get_object_or_404(Agent, agent_id=agent_id)
return Response(
AgentSerializer(agent, context={"default_tz": get_default_timezone()}).data
)
return Response(AgentSerializer(agent).data)
# edit agent
def put(self, request, agent_id):
@@ -185,6 +167,11 @@ class AgentProcesses(APIView):
# list agent processes
def get(self, request, agent_id):
if getattr(settings, "DEMO", False):
from tacticalrmm.demo_views import demo_get_procs
return demo_get_procs()
agent = get_object_or_404(Agent, agent_id=agent_id)
r = asyncio.run(agent.nats_cmd(data={"func": "procs"}, timeout=5))
if r == "timeout" or r == "natsdown":
@@ -311,6 +298,11 @@ def ping(request, agent_id):
@api_view(["GET"])
@permission_classes([IsAuthenticated, EvtLogPerms])
def get_event_log(request, agent_id, logtype, days):
if getattr(settings, "DEMO", False):
from tacticalrmm.demo_views import demo_get_eventlog
return demo_get_eventlog()
agent = get_object_or_404(Agent, agent_id=agent_id)
timeout = 180 if logtype == "Security" else 30
@@ -343,14 +335,13 @@ def send_raw_cmd(request, agent_id):
},
}
if pyver.parse(agent.version) >= pyver.parse("1.6.0"):
hist = AgentHistory.objects.create(
agent=agent,
type="cmd_run",
command=request.data["cmd"],
username=request.user.username[:50],
)
data["id"] = hist.pk
hist = AgentHistory.objects.create(
agent=agent,
type="cmd_run",
command=request.data["cmd"],
username=request.user.username[:50],
)
data["id"] = hist.pk
r = asyncio.run(agent.nats_cmd(data, timeout=timeout + 2))
@@ -621,15 +612,13 @@ def run_script(request, agent_id):
debug_info={"ip": request._client_ip},
)
history_pk = 0
if pyver.parse(agent.version) >= pyver.parse("1.6.0"):
hist = AgentHistory.objects.create(
agent=agent,
type="script_run",
script=script,
username=request.user.username[:50],
)
history_pk = hist.pk
hist = AgentHistory.objects.create(
agent=agent,
type="script_run",
script=script,
username=request.user.username[:50],
)
history_pk = hist.pk
if output == "wait":
r = agent.run_script(

View File

@@ -456,7 +456,8 @@ class Alert(models.Model):
if match:
name = match.group(1)
if hasattr(self, name):
# check if attr exists and isn't a function
if hasattr(self, name) and not callable(getattr(self, name)):
value = f"'{getattr(self, name)}'"
else:
continue
@@ -464,7 +465,7 @@ class Alert(models.Model):
try:
temp_args.append(re.sub("\\{\\{.*\\}\\}", value, arg)) # type: ignore
except Exception as e:
DebugLog.error(log_type="scripting", message=e)
DebugLog.error(log_type="scripting", message=str(e))
continue
else:

View File

@@ -10,12 +10,29 @@ from .models import Alert, AlertTemplate
class AlertSerializer(ModelSerializer):
hostname = SerializerMethodField(read_only=True)
client = SerializerMethodField(read_only=True)
site = SerializerMethodField(read_only=True)
alert_time = SerializerMethodField(read_only=True)
resolve_on = SerializerMethodField(read_only=True)
snoozed_until = SerializerMethodField(read_only=True)
hostname = SerializerMethodField()
agent_id = SerializerMethodField()
client = SerializerMethodField()
site = SerializerMethodField()
alert_time = SerializerMethodField()
resolve_on = SerializerMethodField()
snoozed_until = SerializerMethodField()
def get_agent_id(self, instance):
if instance.alert_type == "availability":
return instance.agent.agent_id if instance.agent else ""
elif instance.alert_type == "check":
return (
instance.assigned_check.agent.agent_id
if instance.assigned_check
else ""
)
elif instance.alert_type == "task":
return (
instance.assigned_task.agent.agent_id if instance.assigned_task else ""
)
else:
return ""
def get_hostname(self, instance):
if instance.alert_type == "availability":

View File

@@ -9,6 +9,7 @@ from model_bakery import baker, seq
from tacticalrmm.test import TacticalTestCase
from alerts.tasks import cache_agents_alert_template
from core.tasks import cache_db_fields_task
from .models import Alert, AlertTemplate
from .serializers import (
@@ -676,25 +677,14 @@ class TestAlertTasks(TacticalTestCase):
url = "/api/v3/checkin/"
agent_template_text.version = settings.LATEST_AGENT_VER
agent_template_text.last_seen = djangotime.now()
agent_template_text.save()
agent_template_email.version = settings.LATEST_AGENT_VER
agent_template_email.last_seen = djangotime.now()
agent_template_email.save()
data = {
"agent_id": agent_template_text.agent_id,
"version": settings.LATEST_AGENT_VER,
}
resp = self.client.patch(url, data, format="json")
self.assertEqual(resp.status_code, 200)
data = {
"agent_id": agent_template_email.agent_id,
"version": settings.LATEST_AGENT_VER,
}
resp = self.client.patch(url, data, format="json")
self.assertEqual(resp.status_code, 200)
cache_db_fields_task()
recovery_sms.assert_called_with(
pk=Alert.objects.get(agent=agent_template_text).pk
@@ -1365,15 +1355,7 @@ class TestAlertTasks(TacticalTestCase):
agent.last_seen = djangotime.now()
agent.save()
url = "/api/v3/checkin/"
data = {
"agent_id": agent.agent_id,
"version": settings.LATEST_AGENT_VER,
}
resp = self.client.patch(url, data, format="json")
self.assertEqual(resp.status_code, 200)
cache_db_fields_task()
# this is what data should be
data = {

View File

@@ -130,42 +130,6 @@ class TestAPIv3(TacticalTestCase):
self.assertIsInstance(r.json()["check_interval"], int)
self.assertEqual(len(r.json()["checks"]), 15)
def test_checkin_patch(self):
from logs.models import PendingAction
url = "/api/v3/checkin/"
agent_updated = baker.make_recipe("agents.agent", version="1.3.0")
PendingAction.objects.create(
agent=agent_updated,
action_type="agentupdate",
details={
"url": agent_updated.winagent_dl,
"version": agent_updated.version,
"inno": agent_updated.win_inno_exe,
},
)
action = agent_updated.pendingactions.filter(action_type="agentupdate").first()
self.assertEqual(action.status, "pending")
# test agent failed to update and still on same version
payload = {
"func": "hello",
"agent_id": agent_updated.agent_id,
"version": "1.3.0",
}
r = self.client.patch(url, payload, format="json")
self.assertEqual(r.status_code, 200)
action = agent_updated.pendingactions.filter(action_type="agentupdate").first()
self.assertEqual(action.status, "pending")
# test agent successful update
payload["version"] = settings.LATEST_AGENT_VER
r = self.client.patch(url, payload, format="json")
self.assertEqual(r.status_code, 200)
action = agent_updated.pendingactions.filter(action_type="agentupdate").first()
self.assertEqual(action.status, "completed")
action.delete()
@patch("apiv3.views.reload_nats")
def test_agent_recovery(self, reload_nats):
reload_nats.return_value = "ok"

View File

@@ -15,15 +15,14 @@ from rest_framework.views import APIView
from accounts.models import User
from agents.models import Agent, AgentHistory
from agents.serializers import WinAgentSerializer, AgentHistorySerializer
from agents.serializers import AgentHistorySerializer
from autotasks.models import AutomatedTask
from autotasks.serializers import TaskGOGetSerializer, TaskRunnerPatchSerializer
from checks.models import Check
from checks.serializers import CheckRunnerGetSerializer
from checks.utils import bytes2human
from logs.models import PendingAction, DebugLog
from software.models import InstalledSoftware
from tacticalrmm.utils import SoftwareList, filter_software, notify_error, reload_nats
from tacticalrmm.utils import notify_error, reload_nats
from winupdate.models import WinUpdate, WinUpdatePolicy
@@ -32,101 +31,6 @@ class CheckIn(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def patch(self, request):
"""
!!! DEPRECATED AS OF AGENT 1.6.0 !!!
Endpoint be removed in a future release
"""
from alerts.models import Alert
updated = False
agent = get_object_or_404(Agent, agent_id=request.data["agent_id"])
if pyver.parse(request.data["version"]) > pyver.parse(
agent.version
) or pyver.parse(request.data["version"]) == pyver.parse(
settings.LATEST_AGENT_VER
):
updated = True
agent.version = request.data["version"]
agent.last_seen = djangotime.now()
agent.save(update_fields=["version", "last_seen"])
# change agent update pending status to completed if agent has just updated
if (
updated
and agent.pendingactions.filter( # type: ignore
action_type="agentupdate", status="pending"
).exists()
):
agent.pendingactions.filter( # type: ignore
action_type="agentupdate", status="pending"
).update(status="completed")
# handles any alerting actions
if Alert.objects.filter(agent=agent, resolved=False).exists():
Alert.handle_alert_resolve(agent)
# sync scheduled tasks
if agent.autotasks.exclude(sync_status="synced").exists(): # type: ignore
tasks = agent.autotasks.exclude(sync_status="synced") # type: ignore
for task in tasks:
if task.sync_status == "pendingdeletion":
task.delete_task_on_agent()
elif task.sync_status == "initial":
task.modify_task_on_agent()
elif task.sync_status == "notsynced":
task.create_task_on_agent()
return Response("ok")
def put(self, request):
agent = get_object_or_404(Agent, agent_id=request.data["agent_id"])
serializer = WinAgentSerializer(instance=agent, data=request.data, partial=True)
if request.data["func"] == "disks":
disks = request.data["disks"]
new = []
for disk in disks:
tmp = {}
for _, _ in disk.items():
tmp["device"] = disk["device"]
tmp["fstype"] = disk["fstype"]
tmp["total"] = bytes2human(disk["total"])
tmp["used"] = bytes2human(disk["used"])
tmp["free"] = bytes2human(disk["free"])
tmp["percent"] = int(disk["percent"])
new.append(tmp)
serializer.is_valid(raise_exception=True)
serializer.save(disks=new)
return Response("ok")
if request.data["func"] == "loggedonuser":
if request.data["logged_in_username"] != "None":
serializer.is_valid(raise_exception=True)
serializer.save(last_logged_in_user=request.data["logged_in_username"])
return Response("ok")
if request.data["func"] == "software":
raw: SoftwareList = request.data["software"]
if not isinstance(raw, list):
return notify_error("err")
sw = filter_software(raw)
if not InstalledSoftware.objects.filter(agent=agent).exists():
InstalledSoftware(agent=agent, software=sw).save()
else:
s = agent.installedsoftware_set.first() # type: ignore
s.software = sw
s.save(update_fields=["software"])
return Response("ok")
serializer.is_valid(raise_exception=True)
serializer.save()
return Response("ok")
# called once during tacticalagent windows service startup
def post(self, request):
agent = get_object_or_404(Agent, agent_id=request.data["agent_id"])
@@ -168,18 +72,18 @@ class WinUpdates(APIView):
def put(self, request):
agent = get_object_or_404(Agent, agent_id=request.data["agent_id"])
needs_reboot: bool = request.data["needs_reboot"]
agent.needs_reboot = needs_reboot
agent.save(update_fields=["needs_reboot"])
reboot_policy: str = agent.get_patch_policy().reboot_after_install
reboot = False
if reboot_policy == "always":
reboot = True
if request.data["needs_reboot"]:
if reboot_policy == "required":
reboot = True
elif reboot_policy == "never":
agent.needs_reboot = True
agent.save(update_fields=["needs_reboot"])
elif needs_reboot and reboot_policy == "required":
reboot = True
if reboot:
asyncio.run(agent.nats_cmd({"func": "rebootnow"}, wait=False))
@@ -249,14 +153,6 @@ class WinUpdates(APIView):
).save()
agent.delete_superseded_updates()
# more superseded updates cleanup
if pyver.parse(agent.version) <= pyver.parse("1.4.2"):
for u in agent.winupdates.filter( # type: ignore
date_installed__isnull=True, result="failed"
).exclude(installed=True):
u.delete()
return Response("ok")
@@ -326,8 +222,6 @@ class CheckRunner(APIView):
def patch(self, request):
check = get_object_or_404(Check, pk=request.data["id"])
if pyver.parse(check.agent.version) < pyver.parse("1.5.7"):
return notify_error("unsupported")
check.last_run = djangotime.now()
check.save(update_fields=["last_run"])
@@ -371,6 +265,13 @@ class TaskRunner(APIView):
serializer.is_valid(raise_exception=True)
new_task = serializer.save(last_run=djangotime.now())
AgentHistory.objects.create(
agent=agent,
type="task_run",
script=task.script,
script_results=request.data,
)
# check if task is a collector and update the custom field
if task.custom_field:
if not task.stderr:
@@ -500,11 +401,7 @@ class Software(APIView):
def post(self, request):
agent = get_object_or_404(Agent, agent_id=request.data["agent_id"])
raw: SoftwareList = request.data["software"]
if not isinstance(raw, list):
return notify_error("err")
sw = filter_software(raw)
sw = request.data["software"]
if not InstalledSoftware.objects.filter(agent=agent).exists():
InstalledSoftware(agent=agent, software=sw).save()
else:
@@ -570,7 +467,18 @@ class AgentRecovery(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, agentid):
agent = get_object_or_404(Agent, agent_id=agentid)
agent = get_object_or_404(
Agent.objects.prefetch_related("recoveryactions").only(
"pk", "agent_id", "last_seen"
),
agent_id=agentid,
)
# TODO remove these 2 lines after agent v1.7.0 has been out for a while
# this is handled now by nats-api service
agent.last_seen = djangotime.now()
agent.save(update_fields=["last_seen"])
recovery = agent.recoveryactions.filter(last_run=None).last() # type: ignore
ret = {"mode": "pass", "shellcmd": ""}
if recovery is None:

View File

@@ -54,6 +54,8 @@ def generate_agent_checks_task(
if create_tasks:
agent.generate_tasks_from_policies()
agent.set_alert_template()
return "ok"

View File

@@ -0,0 +1,87 @@
# Generated by Django 3.2.9 on 2021-12-14 00:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('autotasks', '0023_auto_20210917_1954'),
]
operations = [
migrations.RemoveField(
model_name='automatedtask',
name='run_time_days',
),
migrations.AddField(
model_name='automatedtask',
name='actions',
field=models.JSONField(default=dict),
),
migrations.AddField(
model_name='automatedtask',
name='daily_interval',
field=models.PositiveSmallIntegerField(blank=True, null=True),
),
migrations.AddField(
model_name='automatedtask',
name='expire_date',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name='automatedtask',
name='monthly_days_of_month',
field=models.PositiveIntegerField(blank=True, null=True),
),
migrations.AddField(
model_name='automatedtask',
name='monthly_months_of_year',
field=models.PositiveIntegerField(blank=True, null=True),
),
migrations.AddField(
model_name='automatedtask',
name='monthly_weeks_of_month',
field=models.PositiveSmallIntegerField(blank=True, null=True),
),
migrations.AddField(
model_name='automatedtask',
name='random_task_delay',
field=models.CharField(blank=True, max_length=10, null=True),
),
migrations.AddField(
model_name='automatedtask',
name='stop_task_at_duration_end',
field=models.BooleanField(blank=True, default=False),
),
migrations.AddField(
model_name='automatedtask',
name='task_instance_policy',
field=models.PositiveSmallIntegerField(blank=True, default=1),
),
migrations.AddField(
model_name='automatedtask',
name='task_repetition_duration',
field=models.CharField(blank=True, max_length=10, null=True),
),
migrations.AddField(
model_name='automatedtask',
name='task_repetition_interval',
field=models.CharField(blank=True, max_length=10, null=True),
),
migrations.AddField(
model_name='automatedtask',
name='weekly_interval',
field=models.PositiveSmallIntegerField(blank=True, null=True),
),
migrations.AlterField(
model_name='automatedtask',
name='task_type',
field=models.CharField(choices=[('daily', 'Daily'), ('weekly', 'Weekly'), ('monthly', 'Monthly'), ('monthlydow', 'Monthly Day of Week'), ('checkfailure', 'On Check Failure'), ('manual', 'Manual'), ('runonce', 'Run Once')], default='manual', max_length=100),
),
migrations.AlterField(
model_name='automatedtask',
name='timeout',
field=models.PositiveIntegerField(blank=True, default=120),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.10 on 2021-12-29 14:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('autotasks', '0024_auto_20211214_0040'),
]
operations = [
migrations.AddField(
model_name='automatedtask',
name='continue_on_error',
field=models.BooleanField(default=True),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.10 on 2021-12-30 14:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('autotasks', '0025_automatedtask_continue_on_error'),
]
operations = [
migrations.AlterField(
model_name='automatedtask',
name='monthly_days_of_month',
field=models.PositiveBigIntegerField(blank=True, null=True),
),
]

View File

@@ -0,0 +1,24 @@
# Generated by Django 3.2.11 on 2022-01-07 06:43
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('autotasks', '0026_alter_automatedtask_monthly_days_of_month'),
]
operations = [
migrations.AlterField(
model_name='automatedtask',
name='daily_interval',
field=models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(255)]),
),
migrations.AlterField(
model_name='automatedtask',
name='weekly_interval',
field=models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(52)]),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.11 on 2022-01-09 21:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('autotasks', '0027_auto_20220107_0643'),
]
operations = [
migrations.AlterField(
model_name='automatedtask',
name='actions',
field=models.JSONField(default=list),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.10 on 2022-01-10 01:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('autotasks', '0028_alter_automatedtask_actions'),
]
operations = [
migrations.AlterField(
model_name='automatedtask',
name='task_type',
field=models.CharField(choices=[('daily', 'Daily'), ('weekly', 'Weekly'), ('monthly', 'Monthly'), ('monthlydow', 'Monthly Day of Week'), ('checkfailure', 'On Check Failure'), ('manual', 'Manual'), ('runonce', 'Run Once'), ('scheduled', 'Scheduled')], default='manual', max_length=100),
),
]

View File

@@ -3,6 +3,7 @@ import datetime as dt
import random
import string
from typing import List
from django.db.models.fields.json import JSONField
import pytz
from alerts.models import SEVERITY_CHOICES
@@ -10,27 +11,28 @@ from django.contrib.postgres.fields import ArrayField
from django.db import models
from django.db.models.fields import DateTimeField
from django.db.utils import DatabaseError
from django.core.validators import MaxValueValidator, MinValueValidator
from django.utils import timezone as djangotime
from logs.models import BaseAuditModel, DebugLog
from tacticalrmm.models import PermissionQuerySet
from packaging import version as pyver
from tacticalrmm.utils import bitdays_to_string
RUN_TIME_DAY_CHOICES = [
(0, "Monday"),
(1, "Tuesday"),
(2, "Wednesday"),
(3, "Thursday"),
(4, "Friday"),
(5, "Saturday"),
(6, "Sunday"),
]
from tacticalrmm.utils import (
bitdays_to_string,
bitmonthdays_to_string,
bitmonths_to_string,
bitweeks_to_string,
convert_to_iso_duration,
)
TASK_TYPE_CHOICES = [
("scheduled", "Scheduled"),
("daily", "Daily"),
("weekly", "Weekly"),
("monthly", "Monthly"),
("monthlydow", "Monthly Day of Week"),
("checkfailure", "On Check Failure"),
("manual", "Manual"),
("runonce", "Run Once"),
("scheduled", "Scheduled"), # deprecated
]
SYNC_STATUS_CHOICES = [
@@ -71,6 +73,8 @@ class AutomatedTask(BaseAuditModel):
blank=True,
on_delete=models.SET_NULL,
)
# deprecated
script = models.ForeignKey(
"scripts.Script",
null=True,
@@ -78,12 +82,18 @@ class AutomatedTask(BaseAuditModel):
related_name="autoscript",
on_delete=models.SET_NULL,
)
# deprecated
script_args = ArrayField(
models.CharField(max_length=255, null=True, blank=True),
null=True,
blank=True,
default=list,
)
# deprecated
timeout = models.PositiveIntegerField(blank=True, default=120)
# format -> {"actions": [{"type": "script", "script": 1, "name": "Script Name", "timeout": 90, "script_args": []}, {"type": "cmd", "command": "whoami", "timeout": 90}]}
actions = JSONField(default=list)
assigned_check = models.ForeignKey(
"checks.Check",
null=True,
@@ -92,26 +102,9 @@ class AutomatedTask(BaseAuditModel):
on_delete=models.SET_NULL,
)
name = models.CharField(max_length=255)
run_time_bit_weekdays = models.IntegerField(null=True, blank=True)
# run_time_days is deprecated, use bit weekdays
run_time_days = ArrayField(
models.IntegerField(choices=RUN_TIME_DAY_CHOICES, null=True, blank=True),
null=True,
blank=True,
default=list,
)
run_time_minute = models.CharField(max_length=5, null=True, blank=True)
task_type = models.CharField(
max_length=100, choices=TASK_TYPE_CHOICES, default="manual"
)
collector_all_output = models.BooleanField(default=False)
run_time_date = DateTimeField(null=True, blank=True)
remove_if_not_scheduled = models.BooleanField(default=False)
run_asap_after_missed = models.BooleanField(default=False) # added in agent v1.4.7
managed_by_policy = models.BooleanField(default=False)
parent_task = models.PositiveIntegerField(null=True, blank=True)
win_task_name = models.CharField(max_length=255, null=True, blank=True)
timeout = models.PositiveIntegerField(default=120)
retvalue = models.TextField(null=True, blank=True)
retcode = models.IntegerField(null=True, blank=True)
stdout = models.TextField(null=True, blank=True)
@@ -119,6 +112,7 @@ class AutomatedTask(BaseAuditModel):
execution_time = models.CharField(max_length=100, default="0.0000")
last_run = models.DateTimeField(null=True, blank=True)
enabled = models.BooleanField(default=True)
continue_on_error = models.BooleanField(default=True)
status = models.CharField(
max_length=30, choices=TASK_STATUS_CHOICES, default="pending"
)
@@ -132,33 +126,79 @@ class AutomatedTask(BaseAuditModel):
text_alert = models.BooleanField(default=False)
dashboard_alert = models.BooleanField(default=False)
# options sent to agent for task creation
# general task settings
task_type = models.CharField(
max_length=100, choices=TASK_TYPE_CHOICES, default="manual"
)
win_task_name = models.CharField(max_length=255, null=True, blank=True)
run_time_date = DateTimeField(null=True, blank=True)
expire_date = DateTimeField(null=True, blank=True)
# daily
daily_interval = models.PositiveSmallIntegerField(
blank=True, null=True, validators=[MinValueValidator(1), MaxValueValidator(255)]
)
# weekly
run_time_bit_weekdays = models.IntegerField(null=True, blank=True)
weekly_interval = models.PositiveSmallIntegerField(
blank=True, null=True, validators=[MinValueValidator(1), MaxValueValidator(52)]
)
run_time_minute = models.CharField(
max_length=5, null=True, blank=True
) # deprecated
# monthly
monthly_days_of_month = models.PositiveBigIntegerField(blank=True, null=True)
monthly_months_of_year = models.PositiveIntegerField(blank=True, null=True)
# monthly days of week
monthly_weeks_of_month = models.PositiveSmallIntegerField(blank=True, null=True)
# additional task settings
task_repetition_duration = models.CharField(max_length=10, null=True, blank=True)
task_repetition_interval = models.CharField(max_length=10, null=True, blank=True)
stop_task_at_duration_end = models.BooleanField(blank=True, default=False)
random_task_delay = models.CharField(max_length=10, null=True, blank=True)
remove_if_not_scheduled = models.BooleanField(default=False)
run_asap_after_missed = models.BooleanField(default=False) # added in agent v1.4.7
task_instance_policy = models.PositiveSmallIntegerField(blank=True, default=1)
def __str__(self):
return self.name
def save(self, *args, **kwargs):
from autotasks.tasks import enable_or_disable_win_task
from autotasks.tasks import modify_win_task
from automation.tasks import update_policy_autotasks_fields_task
# get old agent if exists
old_task = AutomatedTask.objects.get(pk=self.pk) if self.pk else None
super(AutomatedTask, self).save(old_model=old_task, *args, **kwargs)
# check if automated task was enabled/disabled and send celery task
if old_task and old_task.enabled != self.enabled:
if self.agent:
enable_or_disable_win_task.delay(pk=self.pk)
# check if fields were updated that require a sync to the agent
update_agent = False
if old_task:
for field in self.fields_that_trigger_task_update_on_agent:
if getattr(self, field) != getattr(old_task, field):
update_agent = True
break
# check if automated task was enabled/disabled and send celery task
if old_task and old_task.agent and update_agent:
modify_win_task.delay(pk=self.pk)
# check if automated task was enabled/disabled and send celery task
elif old_task.policy:
update_policy_autotasks_fields_task.delay(
task=self.pk, update_agent=True
)
# check if policy task was edited and then check if it was a field worth copying to rest of agent tasks
elif old_task and old_task.policy:
for field in self.policy_fields_to_copy:
if getattr(self, field) != getattr(old_task, field):
update_policy_autotasks_fields_task.delay(task=self.pk)
break
if update_agent:
update_policy_autotasks_fields_task.delay(
task=self.pk, update_agent=update_agent
)
else:
for field in self.policy_fields_to_copy:
if getattr(self, field) != getattr(old_task, field):
update_policy_autotasks_fields_task.delay(task=self.pk)
break
@property
def schedule(self):
@@ -168,13 +208,30 @@ class AutomatedTask(BaseAuditModel):
return "Every time check fails"
elif self.task_type == "runonce":
return f'Run once on {self.run_time_date.strftime("%m/%d/%Y %I:%M%p")}'
elif self.task_type == "scheduled":
run_time_nice = dt.datetime.strptime(
self.run_time_minute, "%H:%M"
).strftime("%I:%M %p")
elif self.task_type == "daily":
run_time_nice = self.run_time_date.strftime("%I:%M%p")
if self.daily_interval == 1:
return f"Daily at {run_time_nice}"
else:
return f"Every {self.daily_interval} days at {run_time_nice}"
elif self.task_type == "weekly":
run_time_nice = self.run_time_date.strftime("%I:%M%p")
days = bitdays_to_string(self.run_time_bit_weekdays)
return f"{days} at {run_time_nice}"
if self.weekly_interval != 1:
return f"{days} at {run_time_nice}"
else:
return f"{days} at {run_time_nice} every {self.weekly_interval} weeks"
elif self.task_type == "monthly":
run_time_nice = self.run_time_date.strftime("%I:%M%p")
months = bitmonths_to_string(self.monthly_months_of_year)
days = bitmonthdays_to_string(self.monthly_days_of_month)
return f"Runs on {months} on days {days} at {run_time_nice}"
elif self.task_type == "monthlydow":
run_time_nice = self.run_time_date.strftime("%I:%M%p")
months = bitmonths_to_string(self.monthly_months_of_year)
weeks = bitweeks_to_string(self.monthly_weeks_of_month)
days = bitdays_to_string(self.run_time_bit_weekdays)
return f"Runs on {months} on {weeks} on {days} at {run_time_nice}"
@property
def last_run_as_timezone(self):
@@ -193,22 +250,53 @@ class AutomatedTask(BaseAuditModel):
"email_alert",
"text_alert",
"dashboard_alert",
"script",
"script_args",
"assigned_check",
"name",
"run_time_days",
"run_time_minute",
"actions",
"run_time_bit_weekdays",
"run_time_date",
"expire_date",
"daily_interval",
"weekly_interval",
"task_type",
"win_task_name",
"timeout",
"enabled",
"remove_if_not_scheduled",
"run_asap_after_missed",
"custom_field",
"collector_all_output",
"monthly_days_of_month",
"monthly_months_of_year",
"monthly_weeks_of_month",
"task_repetition_duration",
"task_repetition_interval",
"stop_task_at_duration_end",
"random_task_delay",
"run_asap_after_missed",
"task_instance_policy",
"continue_on_error",
]
@property
def fields_that_trigger_task_update_on_agent(self) -> List[str]:
return [
"run_time_bit_weekdays",
"run_time_date",
"expire_date",
"daily_interval",
"weekly_interval",
"enabled",
"remove_if_not_scheduled",
"run_asap_after_missed",
"monthly_days_of_month",
"monthly_months_of_year",
"monthly_weeks_of_month",
"task_repetition_duration",
"task_repetition_interval",
"stop_task_at_duration_end",
"random_task_delay",
"run_asap_after_missed",
"task_instance_policy",
]
@staticmethod
@@ -261,79 +349,161 @@ class AutomatedTask(BaseAuditModel):
if agent:
task.create_task_on_agent()
# agent version >= 1.8.0
def generate_nats_task_payload(self, editing=False):
task = {
"pk": self.pk,
"type": "rmm",
"name": self.win_task_name,
"overwrite_task": editing,
"enabled": self.enabled,
"trigger": self.task_type if self.task_type != "checkfailure" else "manual",
"multiple_instances": self.task_instance_policy
if self.task_instance_policy
else 0,
"delete_expired_task_after": self.remove_if_not_scheduled
if self.expire_date
else False,
"start_when_available": self.run_asap_after_missed
if self.task_type != "runonce"
else True,
}
if self.task_type in ["runonce", "daily", "weekly", "monthly", "monthlydow"]:
task["start_year"] = int(self.run_time_date.strftime("%Y"))
task["start_month"] = int(self.run_time_date.strftime("%-m"))
task["start_day"] = int(self.run_time_date.strftime("%-d"))
task["start_hour"] = int(self.run_time_date.strftime("%-H"))
task["start_min"] = int(self.run_time_date.strftime("%-M"))
if self.expire_date:
task["expire_year"] = int(self.expire_date.strftime("%Y"))
task["expire_month"] = int(self.expire_date.strftime("%-m"))
task["expire_day"] = int(self.expire_date.strftime("%-d"))
task["expire_hour"] = int(self.expire_date.strftime("%-H"))
task["expire_min"] = int(self.expire_date.strftime("%-M"))
if self.random_task_delay:
task["random_delay"] = convert_to_iso_duration(self.random_task_delay)
if self.task_repetition_interval:
task["repetition_interval"] = convert_to_iso_duration(
self.task_repetition_interval
)
task["repetition_duration"] = convert_to_iso_duration(
self.task_repetition_duration
)
task["stop_at_duration_end"] = self.stop_task_at_duration_end
if self.task_type == "daily":
task["day_interval"] = self.daily_interval
elif self.task_type == "weekly":
task["week_interval"] = self.weekly_interval
task["days_of_week"] = self.run_time_bit_weekdays
elif self.task_type == "monthly":
# check if "last day is configured"
if self.monthly_days_of_month >= 0x80000000:
task["days_of_month"] = self.monthly_days_of_month - 0x80000000
task["run_on_last_day_of_month"] = True
else:
task["days_of_month"] = self.monthly_days_of_month
task["run_on_last_day_of_month"] = False
task["months_of_year"] = self.monthly_months_of_year
elif self.task_type == "monthlydow":
task["days_of_week"] = self.run_time_bit_weekdays
task["months_of_year"] = self.monthly_months_of_year
task["weeks_of_month"] = self.monthly_weeks_of_month
return task
def create_task_on_agent(self):
from agents.models import Agent
agent = (
Agent.objects.filter(pk=self.agent.pk)
.only("pk", "version", "hostname", "agent_id")
.first()
.get()
)
if self.task_type == "scheduled":
if pyver.parse(agent.version) >= pyver.parse("1.8.0"):
nats_data = {
"func": "schedtask",
"schedtaskpayload": {
"type": "rmm",
"trigger": "weekly",
"weekdays": self.run_time_bit_weekdays,
"pk": self.pk,
"name": self.win_task_name,
"hour": dt.datetime.strptime(self.run_time_minute, "%H:%M").hour,
"min": dt.datetime.strptime(self.run_time_minute, "%H:%M").minute,
},
}
elif self.task_type == "runonce":
# check if scheduled time is in the past
agent_tz = pytz.timezone(agent.timezone) # type: ignore
task_time_utc = self.run_time_date.replace(tzinfo=agent_tz).astimezone(
pytz.utc
)
now = djangotime.now()
if task_time_utc < now:
self.run_time_date = now.astimezone(agent_tz).replace(
tzinfo=pytz.utc
) + djangotime.timedelta(minutes=5)
self.save(update_fields=["run_time_date"])
nats_data = {
"func": "schedtask",
"schedtaskpayload": {
"type": "rmm",
"trigger": "once",
"pk": self.pk,
"name": self.win_task_name,
"year": int(dt.datetime.strftime(self.run_time_date, "%Y")),
"month": dt.datetime.strftime(self.run_time_date, "%B"),
"day": int(dt.datetime.strftime(self.run_time_date, "%d")),
"hour": int(dt.datetime.strftime(self.run_time_date, "%H")),
"min": int(dt.datetime.strftime(self.run_time_date, "%M")),
},
}
if self.run_asap_after_missed and pyver.parse(agent.version) >= pyver.parse( # type: ignore
"1.4.7"
):
nats_data["schedtaskpayload"]["run_asap_after_missed"] = True
if self.remove_if_not_scheduled:
nats_data["schedtaskpayload"]["deleteafter"] = True
elif self.task_type == "checkfailure" or self.task_type == "manual":
nats_data = {
"func": "schedtask",
"schedtaskpayload": {
"type": "rmm",
"trigger": "manual",
"pk": self.pk,
"name": self.win_task_name,
},
"schedtaskpayload": self.generate_nats_task_payload(),
}
else:
return "error"
r = asyncio.run(agent.nats_cmd(nats_data, timeout=5)) # type: ignore
if self.task_type == "scheduled":
nats_data = {
"func": "schedtask",
"schedtaskpayload": {
"type": "rmm",
"trigger": "weekly",
"weekdays": self.run_time_bit_weekdays,
"pk": self.pk,
"name": self.win_task_name,
"hour": dt.datetime.strptime(
self.run_time_minute, "%H:%M"
).hour,
"min": dt.datetime.strptime(
self.run_time_minute, "%H:%M"
).minute,
},
}
elif self.task_type == "runonce":
# check if scheduled time is in the past
agent_tz = pytz.timezone(agent.timezone)
task_time_utc = self.run_time_date.replace(tzinfo=agent_tz).astimezone(
pytz.utc
)
now = djangotime.now()
if task_time_utc < now:
self.run_time_date = now.astimezone(agent_tz).replace(
tzinfo=pytz.utc
) + djangotime.timedelta(minutes=5)
self.save(update_fields=["run_time_date"])
nats_data = {
"func": "schedtask",
"schedtaskpayload": {
"type": "rmm",
"trigger": "once",
"pk": self.pk,
"name": self.win_task_name,
"year": int(dt.datetime.strftime(self.run_time_date, "%Y")),
"month": dt.datetime.strftime(self.run_time_date, "%B"),
"day": int(dt.datetime.strftime(self.run_time_date, "%d")),
"hour": int(dt.datetime.strftime(self.run_time_date, "%H")),
"min": int(dt.datetime.strftime(self.run_time_date, "%M")),
},
}
if self.run_asap_after_missed:
nats_data["schedtaskpayload"]["run_asap_after_missed"] = True
if self.remove_if_not_scheduled:
nats_data["schedtaskpayload"]["deleteafter"] = True
elif self.task_type == "checkfailure" or self.task_type == "manual":
nats_data = {
"func": "schedtask",
"schedtaskpayload": {
"type": "rmm",
"trigger": "manual",
"pk": self.pk,
"name": self.win_task_name,
},
}
else:
return "error"
r = asyncio.run(agent.nats_cmd(nats_data, timeout=5))
if r != "ok":
self.sync_status = "initial"
@@ -341,7 +511,7 @@ class AutomatedTask(BaseAuditModel):
DebugLog.warning(
agent=agent,
log_type="agent_issues",
message=f"Unable to create scheduled task {self.name} on {agent.hostname}. It will be created when the agent checks in.", # type: ignore
message=f"Unable to create scheduled task {self.name} on {agent.hostname}. It will be created when the agent checks in.",
)
return "timeout"
else:
@@ -350,7 +520,7 @@ class AutomatedTask(BaseAuditModel):
DebugLog.info(
agent=agent,
log_type="agent_issues",
message=f"{agent.hostname} task {self.name} was successfully created", # type: ignore
message=f"{agent.hostname} task {self.name} was successfully created",
)
return "ok"
@@ -361,17 +531,23 @@ class AutomatedTask(BaseAuditModel):
agent = (
Agent.objects.filter(pk=self.agent.pk)
.only("pk", "version", "hostname", "agent_id")
.first()
.get()
)
nats_data = {
"func": "enableschedtask",
"schedtaskpayload": {
"name": self.win_task_name,
"enabled": self.enabled,
},
}
r = asyncio.run(agent.nats_cmd(nats_data, timeout=5)) # type: ignore
if pyver.parse(agent.version) >= pyver.parse("1.8.0"):
nats_data = {
"func": "schedtask",
"schedtaskpayload": self.generate_nats_task_payload(editing=True),
}
else:
nats_data = {
"func": "enableschedtask",
"schedtaskpayload": {
"name": self.win_task_name,
"enabled": self.enabled,
},
}
r = asyncio.run(agent.nats_cmd(nats_data, timeout=5))
if r != "ok":
self.sync_status = "notsynced"
@@ -379,7 +555,7 @@ class AutomatedTask(BaseAuditModel):
DebugLog.warning(
agent=agent,
log_type="agent_issues",
message=f"Unable to modify scheduled task {self.name} on {agent.hostname}({agent.pk}). It will try again on next agent checkin", # type: ignore
message=f"Unable to modify scheduled task {self.name} on {agent.hostname}({agent.pk}). It will try again on next agent checkin",
)
return "timeout"
else:
@@ -388,7 +564,7 @@ class AutomatedTask(BaseAuditModel):
DebugLog.info(
agent=agent,
log_type="agent_issues",
message=f"{agent.hostname} task {self.name} was successfully modified", # type: ignore
message=f"{agent.hostname} task {self.name} was successfully modified",
)
return "ok"
@@ -399,14 +575,14 @@ class AutomatedTask(BaseAuditModel):
agent = (
Agent.objects.filter(pk=self.agent.pk)
.only("pk", "version", "hostname", "agent_id")
.first()
.get()
)
nats_data = {
"func": "delschedtask",
"schedtaskpayload": {"name": self.win_task_name},
}
r = asyncio.run(agent.nats_cmd(nats_data, timeout=10)) # type: ignore
r = asyncio.run(agent.nats_cmd(nats_data, timeout=10))
if r != "ok" and "The system cannot find the file specified" not in r:
self.sync_status = "pendingdeletion"
@@ -419,7 +595,7 @@ class AutomatedTask(BaseAuditModel):
DebugLog.warning(
agent=agent,
log_type="agent_issues",
message=f"{agent.hostname} task {self.name} will be deleted on next checkin", # type: ignore
message=f"{agent.hostname} task {self.name} will be deleted on next checkin",
)
return "timeout"
else:
@@ -427,7 +603,7 @@ class AutomatedTask(BaseAuditModel):
DebugLog.info(
agent=agent,
log_type="agent_issues",
message=f"{agent.hostname}({agent.pk}) task {self.name} was deleted", # type: ignore
message=f"{agent.hostname}({agent.pk}) task {self.name} was deleted",
)
return "ok"
@@ -438,10 +614,10 @@ class AutomatedTask(BaseAuditModel):
agent = (
Agent.objects.filter(pk=self.agent.pk)
.only("pk", "version", "hostname", "agent_id")
.first()
.get()
)
asyncio.run(agent.nats_cmd({"func": "runtask", "taskpk": self.pk}, wait=False)) # type: ignore
asyncio.run(agent.nats_cmd({"func": "runtask", "taskpk": self.pk}, wait=False))
return "ok"
def save_collector_results(self):

View File

@@ -1,9 +1,6 @@
from rest_framework import serializers
from agents.models import Agent
from checks.serializers import CheckSerializer
from scripts.models import Script
from scripts.serializers import ScriptCheckSerializer
from .models import AutomatedTask
@@ -14,6 +11,153 @@ class TaskSerializer(serializers.ModelSerializer):
schedule = serializers.ReadOnlyField()
last_run = serializers.ReadOnlyField(source="last_run_as_timezone")
alert_template = serializers.SerializerMethodField()
run_time_date = serializers.DateTimeField(format="iso-8601", required=False)
expire_date = serializers.DateTimeField(
format="iso-8601", allow_null=True, required=False
)
def validate_actions(self, value):
if not value:
raise serializers.ValidationError(
f"There must be at least one action configured"
)
for action in value:
if "type" not in action:
raise serializers.ValidationError(
f"Each action must have a type field of either 'script' or 'cmd'"
)
if action["type"] == "script":
if "script" not in action:
raise serializers.ValidationError(
f"A script action type must have a 'script' field with primary key of script"
)
if "script_args" not in action:
raise serializers.ValidationError(
f"A script action type must have a 'script_args' field with an array of arguments"
)
if "timeout" not in action:
raise serializers.ValidationError(
f"A script action type must have a 'timeout' field"
)
if action["type"] == "cmd":
if "command" not in action:
raise serializers.ValidationError(
f"A command action type must have a 'command' field"
)
if "timeout" not in action:
raise serializers.ValidationError(
f"A command action type must have a 'timeout' field"
)
return value
def validate(self, data):
# allow editing with task_type not specified
if self.instance and "task_type" not in data:
# remove schedule related fields from data
if "run_time_date" in data:
del data["run_time_date"]
if "expire_date" in data:
del data["expire_date"]
if "daily_interval" in data:
del data["daily_interval"]
if "weekly_interval" in data:
del data["weekly_interval"]
if "run_time_bit_weekdays" in data:
del data["run_time_bit_weekdays"]
if "monthly_months_of_year" in data:
del data["monthly_months_of_year"]
if "monthly_days_of_month" in data:
del data["monthly_days_of_month"]
if "monthly_weeks_of_month" in data:
del data["monthly_weeks_of_month"]
if "assigned_check" in data:
del data["assigned_check"]
return data
# run_time_date required
if (
data["task_type"] in ["runonce", "daily", "weekly", "monthly", "monthlydow"]
and not data["run_time_date"]
):
raise serializers.ValidationError(
f"run_time_date is required for task_type '{data['task_type']}'"
)
# daily task type validation
if data["task_type"] == "daily":
if "daily_interval" not in data or not data["daily_interval"]:
raise serializers.ValidationError(
f"daily_interval is required for task_type '{data['task_type']}'"
)
# weekly task type validation
elif data["task_type"] == "weekly":
if "weekly_interval" not in data or not data["weekly_interval"]:
raise serializers.ValidationError(
f"weekly_interval is required for task_type '{data['task_type']}'"
)
if "run_time_bit_weekdays" not in data or not data["run_time_bit_weekdays"]:
raise serializers.ValidationError(
f"run_time_bit_weekdays is required for task_type '{data['task_type']}'"
)
# monthly task type validation
elif data["task_type"] == "monthly":
if (
"monthly_months_of_year" not in data
or not data["monthly_months_of_year"]
):
raise serializers.ValidationError(
f"monthly_months_of_year is required for task_type '{data['task_type']}'"
)
if "monthly_days_of_month" not in data or not data["monthly_days_of_month"]:
raise serializers.ValidationError(
f"monthly_days_of_month is required for task_type '{data['task_type']}'"
)
# monthly day of week task type validation
elif data["task_type"] == "monthlydow":
if (
"monthly_months_of_year" not in data
or not data["monthly_months_of_year"]
):
raise serializers.ValidationError(
f"monthly_months_of_year is required for task_type '{data['task_type']}'"
)
if (
"monthly_weeks_of_month" not in data
or not data["monthly_weeks_of_month"]
):
raise serializers.ValidationError(
f"monthly_weeks_of_month is required for task_type '{data['task_type']}'"
)
if "run_time_bit_weekdays" not in data or not data["run_time_bit_weekdays"]:
raise serializers.ValidationError(
f"run_time_bit_weekdays is required for task_type '{data['task_type']}'"
)
# check failure task type validation
elif data["task_type"] == "checkfailure":
if "assigned_check" not in data or not data["assigned_check"]:
raise serializers.ValidationError(
f"assigned_check is required for task_type '{data['task_type']}'"
)
return data
def get_alert_template(self, obj):
@@ -37,34 +181,46 @@ class TaskSerializer(serializers.ModelSerializer):
fields = "__all__"
# below is for the windows agent
class TaskRunnerScriptField(serializers.ModelSerializer):
class Meta:
model = Script
fields = ["id", "filepath", "filename", "shell", "script_type"]
class TaskRunnerGetSerializer(serializers.ModelSerializer):
script = TaskRunnerScriptField(read_only=True)
class Meta:
model = AutomatedTask
fields = ["id", "script", "timeout", "enabled", "script_args"]
class TaskGOGetSerializer(serializers.ModelSerializer):
script = ScriptCheckSerializer(read_only=True)
script_args = serializers.SerializerMethodField()
task_actions = serializers.SerializerMethodField()
def get_script_args(self, obj):
return Script.parse_script_args(
agent=obj.agent, shell=obj.script.shell, args=obj.script_args
)
def get_task_actions(self, obj):
tmp = []
for action in obj.actions:
if action["type"] == "cmd":
tmp.append(
{
"type": "cmd",
"command": Script.parse_script_args(
agent=obj.agent,
shell=action["shell"],
args=[action["command"]],
)[0],
"shell": action["shell"],
"timeout": action["timeout"],
}
)
elif action["type"] == "script":
script = Script.objects.get(pk=action["script"])
tmp.append(
{
"type": "script",
"script_name": script.name,
"code": script.code,
"script_args": Script.parse_script_args(
agent=obj.agent,
shell=script.shell,
args=action["script_args"],
),
"shell": script.shell,
"timeout": action["timeout"],
}
)
return tmp
class Meta:
model = AutomatedTask
fields = ["id", "script", "timeout", "enabled", "script_args"]
fields = ["id", "continue_on_error", "enabled", "task_actions"]
class TaskRunnerPatchSerializer(serializers.ModelSerializer):

View File

@@ -1,6 +1,5 @@
import asyncio
import datetime as dt
from logging import log
import random
from time import sleep
from typing import Union
@@ -22,7 +21,7 @@ def create_win_task_schedule(pk):
@app.task
def enable_or_disable_win_task(pk):
def modify_win_task(pk):
task = AutomatedTask.objects.get(pk=pk)
task.modify_task_on_agent()

View File

@@ -56,6 +56,12 @@ class TestAutotaskViews(TacticalTestCase):
agent = baker.make_recipe("agents.agent")
policy = baker.make("automation.Policy")
check = baker.make_recipe("checks.diskspace_check", agent=agent)
custom_field = baker.make("core.CustomField")
actions = [
{"type": "cmd", "command": "command", "timeout": 30},
{"type": "script", "script": script.id, "script_args": [], "timeout": 90},
]
# test invalid agent
data = {
@@ -65,17 +71,164 @@ class TestAutotaskViews(TacticalTestCase):
resp = self.client.post(url, data, format="json")
self.assertEqual(resp.status_code, 404)
# test add task to agent
# test add task without actions
data = {
"agent": agent.agent_id,
"name": "Test Task Scheduled with Assigned Check",
"run_time_days": ["Sunday", "Monday", "Friday"],
"run_time_minute": "10:00",
"timeout": 120,
"run_time_days": 56,
"enabled": True,
"script": script.id,
"script_args": None,
"task_type": "scheduled",
"actions": [],
"task_type": "manual",
}
resp = self.client.post(url, data, format="json")
self.assertEqual(resp.status_code, 400)
# test add checkfailure task_type to agent without check
data = {
"agent": agent.agent_id,
"name": "Check Failure",
"enabled": True,
"actions": actions,
"task_type": "checkfailure",
}
resp = self.client.post(url, data, format="json")
self.assertEqual(resp.status_code, 400)
create_win_task_schedule.not_assert_called()
# test add manual task_type to agent
data = {
"agent": agent.agent_id,
"name": "Manual",
"enabled": True,
"actions": actions,
"task_type": "manual",
}
resp = self.client.post(url, data, format="json")
self.assertEqual(resp.status_code, 200)
create_win_task_schedule.assert_called()
create_win_task_schedule.reset_mock()
# test add daily task_type to agent
data = {
"agent": agent.agent_id,
"name": "Daily",
"enabled": True,
"actions": actions,
"task_type": "daily",
"daily_interval": 1,
"run_time_date": djangotime.now(),
"repetition_interval": "30M",
"repetition_duration": "1D",
"random_task_delay": "5M",
}
resp = self.client.post(url, data, format="json")
self.assertEqual(resp.status_code, 200)
# test add weekly task_type to agent
data = {
"agent": agent.agent_id,
"name": "Weekly",
"enabled": True,
"actions": actions,
"task_type": "weekly",
"weekly_interval": 2,
"run_time_bit_weekdays": 26,
"run_time_date": djangotime.now(),
"expire_date": djangotime.now(),
"repetition_interval": "30S",
"repetition_duration": "1H",
"random_task_delay": "5M",
"task_instance_policy": 2,
}
resp = self.client.post(url, data, format="json")
self.assertEqual(resp.status_code, 200)
create_win_task_schedule.assert_called()
create_win_task_schedule.reset_mock()
# test add monthly task_type to agent
data = {
"agent": agent.agent_id,
"name": "Monthly",
"enabled": True,
"actions": actions,
"task_type": "monthly",
"monthly_months_of_year": 56,
"monthly_days_of_month": 350,
"run_time_date": djangotime.now(),
"expire_date": djangotime.now(),
"repetition_interval": "30S",
"repetition_duration": "1H",
"random_task_delay": "5M",
}
resp = self.client.post(url, data, format="json")
self.assertEqual(resp.status_code, 200)
create_win_task_schedule.assert_called()
create_win_task_schedule.reset_mock()
# test add monthly day-of-week task_type to agent
data = {
"agent": agent.agent_id,
"name": "Monthly",
"enabled": True,
"actions": actions,
"task_type": "monthlydow",
"monthly_months_of_year": 500,
"monthly_weeks_of_month": 4,
"run_time_bit_weekdays": 15,
"run_time_date": djangotime.now(),
"expire_date": djangotime.now(),
"repetition_interval": "30S",
"repetition_duration": "1H",
"random_task_delay": "5M",
}
resp = self.client.post(url, data, format="json")
self.assertEqual(resp.status_code, 200)
create_win_task_schedule.assert_called()
create_win_task_schedule.reset_mock()
# test add monthly day-of-week task_type to agent with custom field
data = {
"agent": agent.agent_id,
"name": "Monthly",
"enabled": True,
"actions": actions,
"task_type": "monthlydow",
"monthly_months_of_year": 500,
"monthly_weeks_of_month": 4,
"run_time_bit_weekdays": 15,
"run_time_date": djangotime.now(),
"expire_date": djangotime.now(),
"repetition_interval": "30S",
"repetition_duration": "1H",
"random_task_delay": "5M",
"custom_field": custom_field.id,
}
resp = self.client.post(url, data, format="json")
self.assertEqual(resp.status_code, 200)
create_win_task_schedule.assert_called()
create_win_task_schedule.reset_mock()
# test add checkfailure task_type to agent
data = {
"agent": agent.agent_id,
"name": "Check Failure",
"enabled": True,
"actions": actions,
"task_type": "checkfailure",
"assigned_check": check.id,
}
@@ -83,18 +236,15 @@ class TestAutotaskViews(TacticalTestCase):
self.assertEqual(resp.status_code, 200)
create_win_task_schedule.assert_called()
create_win_task_schedule.reset_mock()
# test add task to policy
data = {
"policy": policy.id, # type: ignore
"name": "Test Task Manual",
"run_time_days": [],
"timeout": 120,
"enabled": True,
"script": script.id,
"script_args": None,
"task_type": "manual",
"assigned_check": None,
"actions": actions,
}
resp = self.client.post(url, data, format="json")
@@ -120,16 +270,23 @@ class TestAutotaskViews(TacticalTestCase):
self.check_not_authenticated("get", url)
@patch("autotasks.tasks.enable_or_disable_win_task.delay")
@patch("autotasks.tasks.modify_win_task.delay")
@patch("automation.tasks.update_policy_autotasks_fields_task.delay")
def test_update_autotask(
self, update_policy_autotasks_fields_task, enable_or_disable_win_task
self, update_policy_autotasks_fields_task, modify_win_task
):
# setup data
agent = baker.make_recipe("agents.agent")
agent_task = baker.make("autotasks.AutomatedTask", agent=agent)
policy = baker.make("automation.Policy")
policy_task = baker.make("autotasks.AutomatedTask", enabled=True, policy=policy)
custom_field = baker.make("core.CustomField")
script = baker.make("scripts.Script")
actions = [
{"type": "cmd", "command": "command", "timeout": 30},
{"type": "script", "script": script.id, "script_args": [], "timeout": 90},
]
# test invalid url
resp = self.client.put(f"{base_url}/500/", format="json")
@@ -137,20 +294,64 @@ class TestAutotaskViews(TacticalTestCase):
url = f"{base_url}/{agent_task.id}/" # type: ignore
# test editing task with no task called
# test editing agent task with no task update
data = {"name": "New Name"}
resp = self.client.put(url, data, format="json")
self.assertEqual(resp.status_code, 200)
enable_or_disable_win_task.not_called() # type: ignore
modify_win_task.not_called() # type: ignore
# test editing task
# test editing agent task with agent task update
data = {"enabled": False}
resp = self.client.put(url, data, format="json")
self.assertEqual(resp.status_code, 200)
enable_or_disable_win_task.assert_called_with(pk=agent_task.id) # type: ignore
modify_win_task.assert_called_with(pk=agent_task.id) # type: ignore
modify_win_task.reset_mock()
# test editing agent task with task_type
data = {
"name": "Monthly",
"actions": actions,
"task_type": "monthlydow",
"monthly_months_of_year": 500,
"monthly_weeks_of_month": 4,
"run_time_bit_weekdays": 15,
"run_time_date": djangotime.now(),
"expire_date": djangotime.now(),
"repetition_interval": "30S",
"repetition_duration": "1H",
"random_task_delay": "5M",
"custom_field": custom_field.id,
"run_asap_afteR_missed": False,
}
resp = self.client.put(url, data, format="json")
self.assertEqual(resp.status_code, 200)
modify_win_task.assert_called_with(pk=agent_task.id) # type: ignore
modify_win_task.reset_mock()
# test trying to edit with empty actions
data = {
"name": "Monthly",
"actions": [],
"task_type": "monthlydow",
"monthly_months_of_year": 500,
"monthly_weeks_of_month": 4,
"run_time_bit_weekdays": 15,
"run_time_date": djangotime.now(),
"expire_date": djangotime.now(),
"repetition_interval": "30S",
"repetition_duration": "1H",
"random_task_delay": "5M",
"run_asap_afteR_missed": False,
}
resp = self.client.put(url, data, format="json")
self.assertEqual(resp.status_code, 400)
modify_win_task.assert_not_called # type: ignore
# test editing policy tasks
url = f"{base_url}/{policy_task.id}/" # type: ignore
# test editing policy task
@@ -654,3 +855,9 @@ class TestTaskPermissions(TacticalTestCase):
self.check_authorized("post", url)
self.check_not_authorized("post", unauthorized_url)
def test_policy_fields_to_copy_exists(self):
fields = [i.name for i in AutomatedTask._meta.get_fields()]
task = baker.make("autotasks.AutomatedTask")
for i in task.policy_fields_to_copy: # type: ignore
self.assertIn(i, fields)

View File

@@ -6,7 +6,6 @@ from rest_framework.exceptions import PermissionDenied
from agents.models import Agent
from automation.models import Policy
from tacticalrmm.utils import get_bit_days
from tacticalrmm.permissions import _has_perm_on_agent
from .models import AutomatedTask
@@ -44,17 +43,10 @@ class GetAddAutoTasks(APIView):
data["agent"] = agent.pk
bit_weekdays = None
if "run_time_days" in data.keys():
if data["run_time_days"]:
bit_weekdays = get_bit_days(data["run_time_days"])
data.pop("run_time_days")
serializer = TaskSerializer(data=data)
serializer.is_valid(raise_exception=True)
task = serializer.save(
win_task_name=AutomatedTask.generate_task_name(),
run_time_bit_weekdays=bit_weekdays,
)
if task.agent:

View File

@@ -1,13 +1,9 @@
import json
import os
import string
from statistics import mean
from typing import Any
import pytz
from alerts.models import SEVERITY_CHOICES
from core.models import CoreSettings
from django.conf import settings
from django.contrib.postgres.fields import ArrayField
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models

View File

@@ -1,6 +1,7 @@
from unittest.mock import patch
from django.utils import timezone as djangotime
from django.conf import settings
from model_bakery import baker
from checks.models import CheckHistory
@@ -215,18 +216,12 @@ class TestCheckViews(TacticalTestCase):
@patch("agents.models.Agent.nats_cmd")
def test_run_checks(self, nats_cmd):
agent = baker.make_recipe("agents.agent", version="1.4.1")
agent_b4_141 = baker.make_recipe("agents.agent", version="1.4.0")
url = f"{base_url}/{agent_b4_141.agent_id}/run/"
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
nats_cmd.assert_called_with({"func": "runchecks"}, wait=False)
agent = baker.make_recipe("agents.agent", version=settings.LATEST_AGENT_VER)
nats_cmd.reset_mock()
nats_cmd.return_value = "busy"
url = f"{base_url}/{agent.agent_id}/run/"
r = self.client.get(url)
r = self.client.post(url)
self.assertEqual(r.status_code, 400)
nats_cmd.assert_called_with({"func": "runchecks"}, timeout=15)
self.assertEqual(r.json(), f"Checks are already running on {agent.hostname}")
@@ -234,7 +229,7 @@ class TestCheckViews(TacticalTestCase):
nats_cmd.reset_mock()
nats_cmd.return_value = "ok"
url = f"{base_url}/{agent.agent_id}/run/"
r = self.client.get(url)
r = self.client.post(url)
self.assertEqual(r.status_code, 200)
nats_cmd.assert_called_with({"func": "runchecks"}, timeout=15)
self.assertEqual(r.json(), f"Checks will now be re-run on {agent.hostname}")
@@ -242,12 +237,12 @@ class TestCheckViews(TacticalTestCase):
nats_cmd.reset_mock()
nats_cmd.return_value = "timeout"
url = f"{base_url}/{agent.agent_id}/run/"
r = self.client.get(url)
r = self.client.post(url)
self.assertEqual(r.status_code, 400)
nats_cmd.assert_called_with({"func": "runchecks"}, timeout=15)
self.assertEqual(r.json(), "Unable to contact the agent")
self.check_not_authenticated("get", url)
self.check_not_authenticated("post", url)
def test_get_check_history(self):
# setup data
@@ -1017,7 +1012,8 @@ class TestCheckPermissions(TacticalTestCase):
self.check_not_authorized(method, unauthorized_url)
self.check_authorized(method, policy_url)
def test_check_action_permissions(self):
@patch("agents.models.Agent.nats_cmd")
def test_check_action_permissions(self, nats_cmd):
agent = baker.make_recipe("agents.agent")
unauthorized_agent = baker.make_recipe("agents.agent")
@@ -1096,3 +1092,12 @@ class TestCheckPermissions(TacticalTestCase):
self.check_authorized("patch", url)
self.check_not_authorized("patch", unauthorized_url)
def test_policy_fields_to_copy_exists(self):
from .models import Check
fields = [i.name for i in Check._meta.get_fields()]
check = baker.make("checks.Check")
for i in check.policy_fields_to_copy: # type: ignore
self.assertIn(i, fields)

View File

@@ -4,7 +4,6 @@ from datetime import datetime as dt
from django.db.models import Q
from django.shortcuts import get_object_or_404
from django.utils import timezone as djangotime
from packaging import version as pyver
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
@@ -195,19 +194,15 @@ class GetCheckHistory(APIView):
)
@api_view()
@api_view(["POST"])
@permission_classes([IsAuthenticated, RunChecksPerms])
def run_checks(request, agent_id):
agent = get_object_or_404(Agent, agent_id=agent_id)
if pyver.parse(agent.version) >= pyver.parse("1.4.1"):
r = asyncio.run(agent.nats_cmd({"func": "runchecks"}, timeout=15))
if r == "busy":
return notify_error(f"Checks are already running on {agent.hostname}")
elif r == "ok":
return Response(f"Checks will now be re-run on {agent.hostname}")
else:
return notify_error("Unable to contact the agent")
else:
asyncio.run(agent.nats_cmd({"func": "runchecks"}, wait=False))
r = asyncio.run(agent.nats_cmd({"func": "runchecks"}, timeout=15))
if r == "busy":
return notify_error(f"Checks are already running on {agent.hostname}")
elif r == "ok":
return Response(f"Checks will now be re-run on {agent.hostname}")
else:
return notify_error("Unable to contact the agent")

View File

@@ -0,0 +1,34 @@
# Generated by Django 3.2.10 on 2021-12-26 05:47
import clients.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('clients', '0019_remove_deployment_client'),
]
operations = [
migrations.AddField(
model_name='client',
name='agent_count',
field=models.PositiveIntegerField(default=0),
),
migrations.AddField(
model_name='client',
name='failing_checks',
field=models.JSONField(default=clients.models._default_failing_checks_data),
),
migrations.AddField(
model_name='site',
name='agent_count',
field=models.PositiveIntegerField(default=0),
),
migrations.AddField(
model_name='site',
name='failing_checks',
field=models.JSONField(default=clients.models._default_failing_checks_data),
),
]

View File

@@ -6,6 +6,11 @@ from django.db import models
from agents.models import Agent
from logs.models import BaseAuditModel
from tacticalrmm.models import PermissionQuerySet
from tacticalrmm.utils import AGENT_DEFER
def _default_failing_checks_data():
return {"error": False, "warning": False}
class Client(BaseAuditModel):
@@ -13,6 +18,8 @@ class Client(BaseAuditModel):
name = models.CharField(max_length=255, unique=True)
block_policy_inheritance = models.BooleanField(default=False)
failing_checks = models.JSONField(default=_default_failing_checks_data)
agent_count = models.PositiveIntegerField(default=0)
workstation_policy = models.ForeignKey(
"automation.Policy",
related_name="workstation_clients",
@@ -71,58 +78,18 @@ class Client(BaseAuditModel):
def __str__(self):
return self.name
@property
def agent_count(self) -> int:
return Agent.objects.filter(site__client=self).count()
@property
def has_maintenanace_mode_agents(self):
return (
Agent.objects.filter(site__client=self, maintenance_mode=True).count() > 0
Agent.objects.defer(*AGENT_DEFER)
.filter(site__client=self, maintenance_mode=True)
.count()
> 0
)
@property
def has_failing_checks(self):
agents = (
Agent.objects.only(
"pk",
"overdue_email_alert",
"overdue_text_alert",
"last_seen",
"overdue_time",
"offline_time",
)
.filter(site__client=self)
.prefetch_related("agentchecks", "autotasks")
)
data = {"error": False, "warning": False}
for agent in agents:
if agent.maintenance_mode:
break
if agent.overdue_email_alert or agent.overdue_text_alert:
if agent.status == "overdue":
data["error"] = True
break
if agent.checks["has_failing_checks"]:
if agent.checks["warning"]:
data["warning"] = True
if agent.checks["failing"]:
data["error"] = True
break
if agent.autotasks.exists(): # type: ignore
for i in agent.autotasks.all(): # type: ignore
if i.status == "failing" and i.alert_severity == "error":
data["error"] = True
break
return data
def live_agent_count(self) -> int:
return Agent.objects.defer(*AGENT_DEFER).filter(site__client=self).count()
@staticmethod
def serialize(client):
@@ -138,6 +105,8 @@ class Site(BaseAuditModel):
client = models.ForeignKey(Client, related_name="sites", on_delete=models.CASCADE)
name = models.CharField(max_length=255)
block_policy_inheritance = models.BooleanField(default=False)
failing_checks = models.JSONField(default=_default_failing_checks_data)
agent_count = models.PositiveIntegerField(default=0)
workstation_policy = models.ForeignKey(
"automation.Policy",
related_name="workstation_sites",
@@ -192,55 +161,13 @@ class Site(BaseAuditModel):
def __str__(self):
return self.name
@property
def agent_count(self) -> int:
return Agent.objects.filter(site=self).count()
@property
def has_maintenanace_mode_agents(self):
return Agent.objects.filter(site=self, maintenance_mode=True).count() > 0
return self.agents.defer(*AGENT_DEFER).filter(maintenance_mode=True).count() > 0 # type: ignore
@property
def has_failing_checks(self):
agents = (
Agent.objects.only(
"pk",
"overdue_email_alert",
"overdue_text_alert",
"last_seen",
"overdue_time",
"offline_time",
)
.filter(site=self)
.prefetch_related("agentchecks", "autotasks")
)
data = {"error": False, "warning": False}
for agent in agents:
if agent.maintenance_mode:
break
if agent.overdue_email_alert or agent.overdue_text_alert:
if agent.status == "overdue":
data["error"] = True
break
if agent.checks["has_failing_checks"]:
if agent.checks["warning"]:
data["warning"] = True
if agent.checks["failing"]:
data["error"] = True
break
if agent.autotasks.exists(): # type: ignore
for i in agent.autotasks.all(): # type: ignore
if i.status == "failing" and i.alert_severity == "error":
data["error"] = True
break
return data
def live_agent_count(self) -> int:
return self.agents.defer(*AGENT_DEFER).count() # type: ignore
@staticmethod
def serialize(site):

View File

@@ -30,9 +30,7 @@ class SiteCustomFieldSerializer(ModelSerializer):
class SiteSerializer(ModelSerializer):
client_name = ReadOnlyField(source="client.name")
custom_fields = SiteCustomFieldSerializer(many=True, read_only=True)
agent_count = ReadOnlyField()
maintenance_mode = ReadOnlyField(source="has_maintenanace_mode_agents")
failing_checks = ReadOnlyField(source="has_failing_checks")
class Meta:
model = Site
@@ -94,9 +92,7 @@ class ClientCustomFieldSerializer(ModelSerializer):
class ClientSerializer(ModelSerializer):
sites = SerializerMethodField()
custom_fields = ClientCustomFieldSerializer(many=True, read_only=True)
agent_count = ReadOnlyField()
maintenance_mode = ReadOnlyField(source="has_maintenanace_mode_agents")
failing_checks = ReadOnlyField(source="has_failing_checks")
def get_sites(self, obj):
return SiteSerializer(

View File

@@ -126,15 +126,16 @@ class GetUpdateDeleteClient(APIView):
from automation.tasks import generate_agent_checks_task
client = get_object_or_404(Client, pk=pk)
agent_count = client.live_agent_count
# only run tasks if it affects clients
if client.agent_count > 0 and "move_to_site" in request.query_params.keys():
if agent_count > 0 and "move_to_site" in request.query_params.keys():
agents = Agent.objects.filter(site__client=client)
site = get_object_or_404(Site, pk=request.query_params["move_to_site"])
agents.update(site=site)
generate_agent_checks_task.delay(all=True, create_tasks=True)
elif client.agent_count > 0:
elif agent_count > 0:
return notify_error(
"Agents exist under this client. There needs to be a site specified to move existing agents to"
)
@@ -230,13 +231,14 @@ class GetUpdateDeleteSite(APIView):
return notify_error("A client must have at least 1 site.")
# only run tasks if it affects clients
if site.agent_count > 0 and "move_to_site" in request.query_params.keys():
agent_count = site.live_agent_count
if agent_count > 0 and "move_to_site" in request.query_params.keys():
agents = Agent.objects.filter(site=site)
new_site = get_object_or_404(Site, pk=request.query_params["move_to_site"])
agents.update(site=new_site)
generate_agent_checks_task.delay(all=True, create_tasks=True)
elif site.agent_count > 0:
elif agent_count > 0:
return notify_error(
"There needs to be a site specified to move the agents to"
)

View File

@@ -0,0 +1,24 @@
import os
import json
from django.core.management.base import BaseCommand
from django.conf import settings
class Command(BaseCommand):
help = "Generate conf for nats-api"
def handle(self, *args, **kwargs):
db = settings.DATABASES["default"]
config = {
"key": settings.SECRET_KEY,
"natsurl": f"tls://{settings.ALLOWED_HOSTS[0]}:4222",
"user": db["USER"],
"pass": db["PASSWORD"],
"host": db["HOST"],
"port": int(db["PORT"]),
"dbname": db["NAME"],
}
conf = os.path.join(settings.BASE_DIR, "nats-api.conf")
with open(conf, "w") as f:
json.dump(config, f)

View File

@@ -17,8 +17,7 @@ class Command(BaseCommand):
token = get_auth_token(mesh_settings.mesh_username, mesh_settings.mesh_token)
if settings.DOCKER_BUILD:
site = mesh_settings.mesh_site.replace("https", "ws")
uri = f"{site}:443/control.ashx?auth={token}"
uri = f"{settings.MESH_WS_URL}/control.ashx?auth={token}"
else:
site = mesh_settings.mesh_site.replace("https", "wss")
uri = f"{site}/control.ashx?auth={token}"

View File

@@ -18,8 +18,7 @@ class Command(BaseCommand):
token = get_auth_token(mesh_settings.mesh_username, mesh_settings.mesh_token)
if settings.DOCKER_BUILD:
site = mesh_settings.mesh_site.replace("https", "ws")
uri = f"{site}:443/control.ashx?auth={token}"
uri = f"{settings.MESH_WS_URL}/control.ashx?auth={token}"
else:
site = mesh_settings.mesh_site.replace("https", "wss")
uri = f"{site}/control.ashx?auth={token}"

View File

@@ -0,0 +1,18 @@
from scripts.models import Script
from django.core.management.base import BaseCommand
from django.conf import settings
class Command(BaseCommand):
help = "loads scripts for demo"
def handle(self, *args, **kwargs):
scripts_dir = settings.BASE_DIR.joinpath("tacticalrmm/test_data/demo_scripts")
scripts = Script.objects.filter(script_type="userdefined")
for script in scripts:
filepath = scripts_dir.joinpath(script.filename)
with open(filepath, "rb") as f:
script.script_body = f.read().decode("utf-8")
script.save(update_fields=["script_body"])
self.stdout.write(self.style.SUCCESS("Added userdefined scripts"))

View File

@@ -1,7 +1,11 @@
import base64
from django.core.management.base import BaseCommand
from django.utils.timezone import make_aware
import datetime as dt
from logs.models import PendingAction
from scripts.models import Script
from autotasks.models import AutomatedTask
from accounts.models import User
@@ -20,3 +24,44 @@ class Command(BaseCommand):
for user in User.objects.filter(is_installer_user=True):
user.block_dashboard_login = True
user.save()
# convert script base64 field to text field
user_scripts = Script.objects.exclude(script_type="builtin").filter(
script_body=""
)
for script in user_scripts:
# decode base64 string
script.script_body = base64.b64decode(
script.code_base64.encode("ascii", "ignore")
).decode("ascii", "ignore")
# script.hash_script_body() # also saves script
script.save(update_fields=["script_body"])
# convert autotask to the new format
for task in AutomatedTask.objects.all():
edited = False
# convert scheduled task_type
if task.task_type == "scheduled":
task.task_type = "daily"
task.run_time_date = make_aware(
dt.datetime.strptime(task.run_time_minute, "%H:%M")
)
task.daily_interval = 1
edited = True
# convert actions
if not task.actions:
task.actions = [
{
"type": "script",
"script": task.script.pk,
"script_args": task.script_args,
"timeout": task.timeout,
"name": task.script.name,
}
]
edited = True
if edited:
task.save()

View File

@@ -119,7 +119,6 @@ class CoreSettings(BaseAuditModel):
def sms_is_configured(self):
return all(
[
self.sms_alert_recipients,
self.twilio_auth_token,
self.twilio_account_sid,
self.twilio_number,
@@ -131,7 +130,6 @@ class CoreSettings(BaseAuditModel):
# smtp with username/password authentication
if (
self.smtp_requires_auth
and self.email_alert_recipients
and self.smtp_from_email
and self.smtp_host
and self.smtp_host_user
@@ -142,7 +140,6 @@ class CoreSettings(BaseAuditModel):
# smtp relay
elif (
not self.smtp_requires_auth
and self.email_alert_recipients
and self.smtp_from_email
and self.smtp_host
and self.smtp_port

View File

@@ -1,5 +1,7 @@
import pytz
from django.utils import timezone as djangotime
from django.conf import settings
from packaging import version as pyver
from autotasks.models import AutomatedTask
from autotasks.tasks import delete_win_task_schedule
@@ -9,6 +11,10 @@ from alerts.tasks import prune_resolved_alerts
from core.models import CoreSettings
from logs.tasks import prune_debug_log, prune_audit_log
from tacticalrmm.celery import app
from tacticalrmm.utils import AGENT_DEFER
from agents.models import Agent
from clients.models import Client, Site
from alerts.models import Alert
@app.task
@@ -54,13 +60,89 @@ def core_maintenance_tasks():
clear_faults_task.delay(core.clear_faults_days) # type: ignore
def _get_failing_data(agents):
data = {"error": False, "warning": False}
for agent in agents:
if agent.maintenance_mode:
break
if agent.overdue_email_alert or agent.overdue_text_alert:
if agent.status == "overdue":
data["error"] = True
break
if agent.checks["has_failing_checks"]:
if agent.checks["warning"]:
data["warning"] = True
if agent.checks["failing"]:
data["error"] = True
break
if agent.autotasks.exists(): # type: ignore
for i in agent.autotasks.all(): # type: ignore
if i.status == "failing" and i.alert_severity == "error":
data["error"] = True
break
return data
@app.task
def cache_db_fields_task():
from agents.models import Agent
# update client/site failing check fields and agent counts
for site in Site.objects.all():
agents = site.agents.defer(*AGENT_DEFER)
site.failing_checks = _get_failing_data(agents)
site.agent_count = agents.count()
site.save(update_fields=["failing_checks", "agent_count"])
for agent in Agent.objects.prefetch_related("winupdates", "pendingactions").only(
"pending_actions_count", "has_patches_pending", "pk"
):
for client in Client.objects.all():
agents = Agent.objects.defer(*AGENT_DEFER).filter(site__client=client)
client.failing_checks = _get_failing_data(agents)
client.agent_count = agents.count()
client.save(update_fields=["failing_checks", "agent_count"])
for agent in Agent.objects.defer(*AGENT_DEFER):
if (
pyver.parse(agent.version) >= pyver.parse("1.6.0")
and agent.status == "online"
):
# change agent update pending status to completed if agent has just updated
if (
pyver.parse(agent.version) == pyver.parse(settings.LATEST_AGENT_VER)
and agent.pendingactions.filter(
action_type="agentupdate", status="pending"
).exists()
):
agent.pendingactions.filter(
action_type="agentupdate", status="pending"
).update(status="completed")
# sync scheduled tasks
if agent.autotasks.exclude(sync_status="synced").exists(): # type: ignore
tasks = agent.autotasks.exclude(sync_status="synced") # type: ignore
for task in tasks:
try:
if task.sync_status == "pendingdeletion":
task.delete_task_on_agent()
elif task.sync_status == "initial":
task.modify_task_on_agent()
elif task.sync_status == "notsynced":
task.create_task_on_agent()
except:
continue
# handles any alerting actions
if Alert.objects.filter(agent=agent, resolved=False).exists():
try:
Alert.handle_alert_resolve(agent)
except:
continue
# update pending patches and pending action counts
agent.pending_actions_count = agent.pendingactions.filter(
status="pending"
).count()

View File

@@ -98,7 +98,7 @@ def dashboard_info(request):
"client_tree_splitter": request.user.client_tree_splitter,
"loading_bar_color": request.user.loading_bar_color,
"clear_search_when_switching": request.user.clear_search_when_switching,
"hosted": hasattr(settings, "HOSTED") and settings.HOSTED,
"hosted": getattr(settings, "HOSTED", False),
}
)

View File

@@ -9,7 +9,7 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.exceptions import PermissionDenied
from tacticalrmm.utils import notify_error, get_default_timezone
from tacticalrmm.utils import notify_error, get_default_timezone, AGENT_DEFER
from tacticalrmm.permissions import _audit_log_filter, _has_perm_on_agent
from .models import AuditLog, PendingAction, DebugLog
@@ -93,10 +93,16 @@ class PendingActions(APIView):
def get(self, request, agent_id=None):
if agent_id:
agent = get_object_or_404(Agent, agent_id=agent_id)
agent = get_object_or_404(
Agent.objects.defer(*AGENT_DEFER), agent_id=agent_id
)
actions = PendingAction.objects.filter(agent=agent)
else:
actions = PendingAction.objects.filter_by_role(request.user)
actions = (
PendingAction.objects.select_related("agent")
.defer("agent__services", "agent__wmi_detail")
.filter_by_role(request.user) # type: ignore
)
return Response(PendingActionSerializer(actions, many=True).data)

View File

@@ -8,4 +8,3 @@ Pygments
isort
mypy
types-pytz
types-pytz

View File

@@ -1,37 +1,42 @@
asgiref==3.4.1
asyncio-nats-client==0.11.4
celery==5.1.2
asyncio-nats-client==0.11.5
celery==5.2.3
certifi==2021.10.8
cffi==1.15.0
channels==3.0.4
channels_redis==3.3.1
chardet==4.0.0
cryptography==3.4.8
cryptography==36.0.1
daphne==3.0.2
Django==3.2.9
django-cors-headers==3.10.0
django-ipware==4.0.0
<<<<<<< HEAD
Django==3.2.10
=======
Django==3.2.11
>>>>>>> develop
django-cors-headers==3.10.1
django-ipware==4.0.2
django-rest-knox==4.1.0
djangorestframework==3.12.4
djangorestframework==3.13.1
future==0.18.2
loguru==0.5.3
msgpack==1.0.2
packaging==21.2
psycopg2-binary==2.9.1
msgpack==1.0.3
packaging==21.3
psycopg2-binary==2.9.3
pycparser==2.21
pycryptodome==3.11.0
pycryptodome==3.12.0
pyotp==2.6.0
pyparsing==2.4.7
pyparsing==3.0.6
pytz==2021.3
qrcode==6.1
redis==3.5.3
requests==2.26.0
qrcode==7.3.1
redis==4.1.0
requests==2.27.1
six==1.16.0
sqlparse==0.4.2
twilio==7.3.0
urllib3==1.26.7
twilio==7.4.0
urllib3==1.26.8
uWSGI==2.0.20
validators==0.18.2
vine==5.0.0
websockets==9.1
zipp==3.6.0
websockets==10.1
zipp==3.7.0
drf_spectacular==0.21.1

View File

@@ -9,6 +9,16 @@
"category": "TRMM (Win):Browsers",
"default_timeout": "300"
},
{
"guid": "720edbb7-8faf-4a77-9283-29935e8880d0",
"filename": "Win_Printer_ClearandRestart.bat",
"submittedBy": "https://github.com/wh1te909",
"name": "Printers - Clear all print jobs",
"description": "This script will stop the spooler, delete all pending print jobs and restart the spooler",
"shell": "cmd",
"category": "TRMM (Win):Printing",
"default_timeout": "300"
},
{
"guid": "3ff6a386-11d1-4f9d-8cca-1b0563bb6443",
"filename": "Win_Google_Chrome_Clear_Cache.ps1",
@@ -19,6 +29,16 @@
"category": "TRMM (Win):Browsers",
"default_timeout": "300"
},
{
"guid": "d3c74105-d1e5-40d8-94ff-b4d6b216fe0f",
"filename": "Win_Chocolatey_List_Installed.bat",
"submittedBy": "https://github.com/silversword411",
"name": "Chocolatey - List Installed apps",
"description": "Lists apps locally installed by chocolatey",
"shell": "cmd",
"category": "TRMM (Win):3rd Party Software>Chocolatey",
"default_timeout": "90"
},
{
"guid": "be1de837-f677-4ac5-aa0c-37a0fc9991fc",
"filename": "Win_Install_Adobe_Reader.ps1",
@@ -48,6 +68,16 @@
"shell": "powershell",
"category": "TRMM (Win):3rd Party Software>Monitoring"
},
{
"guid": "5a60c13b-1882-4a92-bdfb-6dd1f6a11dd14",
"filename": "Win_Windows_Update_RevertToDefault.ps1",
"submittedBy": "https://github.com/silversword411",
"name": "Windows Update - Re-enable Microsoft managed Windows Update",
"description": "TRMM agent will set registry key to disable Windows Auto Updates. This will re-enable Windows standard update settings",
"shell": "powershell",
"category": "TRMM (Win):Updates",
"default_timeout": "90"
},
{
"guid": "81cc5bcb-01bf-4b0c-89b9-0ac0f3fe0c04",
"filename": "Win_Windows_Update_Reset.ps1",
@@ -63,7 +93,7 @@
"filename": "Win_Start_Cleanup.ps1",
"submittedBy": "https://github.com/Omnicef",
"name": "Disk - Cleanup C: drive",
"description": "Cleans the C: drive's Window Temperary files, Windows SoftwareDistribution folder, the local users Temperary folder, IIS logs (if applicable) and empties the recycling bin. All deleted files will go into a log transcript in $env:TEMP. By default this script leaves files that are newer than 7 days old however this variable can be edited.",
"description": "Cleans the C: drive's Window Temporary files, Windows SoftwareDistribution folder, the local users Temperary folder, IIS logs (if applicable) and empties the recycling bin. All deleted files will go into a log transcript in $env:TEMP. By default this script leaves files that are newer than 7 days old however this variable can be edited.",
"shell": "powershell",
"category": "TRMM (Win):Maintenance",
"default_timeout": "25000"
@@ -102,9 +132,7 @@
"submittedBy": "https://github.com/bradhawkins85",
"name": "TacticalRMM - Agent Rename",
"description": "Updates the DisplayName registry entry for the Tactical RMM windows agent to your desired name. This script takes 1 required argument: the name you wish to set.",
"args": [
"<string>"
],
"syntax": "<string>",
"shell": "powershell",
"category": "TRMM (Win):TacticalRMM Related"
},
@@ -114,9 +142,7 @@
"submittedBy": "https://github.com/silversword411",
"name": "Bitlocker - Check Drive for Status",
"description": "Runs a check on drive for Bitlocker status. Returns 0 if Bitlocker is not enabled, 1 if Bitlocker is enabled",
"args": [
"[Drive <string>]"
],
"syntax": "[Drive <string>]",
"shell": "powershell",
"category": "TRMM (Win):Storage"
},
@@ -147,6 +173,15 @@
"shell": "powershell",
"category": "TRMM (Win):Storage"
},
{
"guid": "11be7136-0416-47b4-a6dd-9776fa857dca",
"filename": "Win_Storage_CheckPools.ps1",
"submittedBy": "https://github.com/wh1te909",
"name": "Storage Pools - Check Health",
"description": "Checks all storage pools for health, returns error 1 if unhealthy",
"shell": "powershell",
"category": "TRMM (Win):Monitoring"
},
{
"guid": "cfa14c28-4dfc-4d4e-95ee-a380652e058d",
"filename": "Win_Bios_Check.ps1",
@@ -174,6 +209,15 @@
"shell": "powershell",
"category": "TRMM (Win):Hardware"
},
{
"guid": "2cf918d1-1cc8-4208-bc94-9ca7b34e61c2",
"filename": "Win_Lenovo_Driver_Updates.ps1",
"submittedBy": "https://github.com/maltekiefer",
"name": "Lenovo - Driver Updates",
"description": "Searches the Lenovo support system for new drivers and installs them",
"shell": "powershell",
"category": "TRMM (Win):Hardware"
},
{
"guid": "72c56717-28ed-4cc6-b30f-b362d30fb4b6",
"filename": "Win_Hardware_SN.ps1",
@@ -188,19 +232,31 @@
"filename": "Win_Screenconnect_GetGUID.ps1",
"submittedBy": "https://github.com/silversword411",
"name": "Screenconnect - Get GUID for client",
"description": "Returns Screenconnect GUID for client - Use with Custom Fields for later use. ",
"description": "Returns Screenconnect GUID for client - Use with Custom Fields for later use.",
"args": [
"{{client.ScreenConnectService}}"
],
"shell": "powershell",
"category": "TRMM (Win):Collectors"
},
{
"guid": "bbe5645f-c8d8-4d86-bddd-c8dbea45c974",
"filename": "Win_Splashtop_Get_ID.ps1",
"submittedBy": "https://github.com/r3die",
"name": "Splashtop - Get SUUID for client",
"description": "Returns Splashtop SUUID for client - Use with Custom Fields for later use.",
"args": [
"{{agent.SplashtopSUUID}}"
],
"shell": "powershell",
"category": "TRMM (Win):Collectors"
},
{
"guid": "9cfdfe8f-82bf-4081-a59f-576d694f4649",
"filename": "Win_Teamviewer_Get_ID.ps1",
"submittedBy": "https://github.com/silversword411",
"name": "TeamViewer - Get ClientID for client",
"description": "Returns Teamviwer ClientID for client - Use with Custom Fields for later use. ",
"description": "Returns Teamviewer ClientID for client - Use with Custom Fields for later use.",
"shell": "powershell",
"category": "TRMM (Win):Collectors"
},
@@ -209,7 +265,16 @@
"filename": "Win_AnyDesk_Get_Anynet_ID.ps1",
"submittedBy": "https://github.com/meuchels",
"name": "AnyDesk - Get AnyNetID for client",
"description": "Returns AnyNetID for client - Use with Custom Fields for later use. ",
"description": "Returns AnyNetID for client - Use with Custom Fields for later use.",
"shell": "powershell",
"category": "TRMM (Win):Collectors"
},
{
"guid": "672403e4-f9b5-4442-8d8c-4fb376dd0a62",
"filename": "Win_Securepoint_Get_DeviceId.ps1",
"submittedBy": "https://github.com/maltekiefer",
"name": "Securepoint - Get DeviceId for client",
"description": "Returns Securepoint DeviceId for client - Use with Custom Fields for later use.",
"shell": "powershell",
"category": "TRMM (Win):Collectors"
},
@@ -241,21 +306,43 @@
"category": "TRMM (Win):Updates",
"default_timeout": "25000"
},
{
"guid": "4d0ba685-2259-44be-9010-8ed2fa48bf74",
"filename": "Win_Win11_Ready.ps1",
"submittedBy": "https://github.com/adamjrberry/",
"name": "Windows 11 Upgrade capable check",
"description": "Checks to see if machine is Win11 capable",
"shell": "powershell",
"category": "TRMM (Win):Updates",
"default_timeout": "3600"
},
{
"guid": "375323e5-cac6-4f35-a304-bb7cef35902d",
"filename": "Win_Disk_Status.ps1",
"filename": "Win_Disk_Volume_Status.ps1",
"submittedBy": "https://github.com/dinger1986",
"name": "Disk Hardware Health Check (using Event Viewer errors)",
"description": "Checks local disks for errors reported in event viewer within the last 24 hours",
"name": "Disk Drive Volume Health Check (using Event Viewer errors)",
"description": "Checks Drive Volumes for errors reported in event viewer within the last 24 hours",
"shell": "powershell",
"category": "TRMM (Win):Hardware"
},
{
"guid": "4ace28ee-98f7-4931-9ac9-0adaf1a757ed",
"filename": "Win_Software_Install_Report.ps1",
"submittedBy": "https://github.com/silversword",
"name": "Software Install - Reports new installs",
"description": "This will check for software install events in the application Event Viewer log. If a number is provided as a command parameter it will search that number of days back.",
"syntax": "[<int>]",
"shell": "powershell",
"category": "TRMM (Win):Monitoring",
"default_timeout": "90"
},
{
"guid": "907652a5-9ec1-4759-9871-a7743f805ff2",
"filename": "Win_Software_Uninstall.ps1",
"submittedBy": "https://github.com/subzdev",
"name": "Software Uninstaller - list, find, and uninstall most software",
"description": "Allows listing, finding and uninstalling most software on Windows. There will be a best effort to uninstall silently if the silent uninstall string is not provided.",
"syntax": "-list <string>\n[-u <uninstall string>]\n[-u quiet <uninstall string>]",
"shell": "powershell",
"category": "TRMM (Win):3rd Party Software",
"default_timeout": "600"
@@ -266,6 +353,7 @@
"submittedBy": "https://github.com/jhtechIL/",
"name": "BitDefender Gravity Zone Install",
"description": "Installs BitDefender Gravity Zone, requires client custom field setup. See script comments for details",
"syntax": "[-log]",
"args": [
"-url {{client.bdurl}}",
"-exe {{client.bdexe}}"
@@ -274,10 +362,37 @@
"shell": "powershell",
"category": "TRMM (Win):3rd Party Software"
},
{
"guid": "bfd61545-839b-45da-8b3d-75ffc4d43272",
"filename": "Win_Sophos_EndpointProtection_Install.ps1",
"submittedBy": "https://github.com/bc24fl/",
"name": "Sophos Endpoint Protection Install",
"description": "Installs Sophos Endpoint Protection via the Sophos API. Products include Antivirus, InterceptX, MDR, Device Encryption. The script requires API credentials, Custom Fields, and Arguments passed to script. See script comments for details",
"args": [
"-ClientId {{client.SophosClientId}}",
"-ClientSecret {{client.SophosClientSecret}}",
"-TenantName {{client.SophosTenantName}}",
"-Products antivirus,intercept"
],
"default_timeout": "3600",
"shell": "powershell",
"category": "TRMM (Win):3rd Party Software"
},
{
"guid": "a9d2a6c0-8afa-4d69-8faf-f83b49c11702",
"filename": "Win_Printer_Restart_Jobs.ps1",
"submittedBy": "https://github.com/bc24fl/",
"name": "Printers - Restarts stuck printer jobs.",
"description": "Cycles through each printer and restarts any jobs that are stuck with error status.",
"shell": "powershell",
"category": "TRMM (Win):Printing",
"default_timeout": "90"
},
{
"guid": "da51111c-aff6-4d87-9d76-0608e1f67fe5",
"filename": "Win_Defender_Enable.ps1",
"submittedBy": "https://github.com/dinger1986",
"syntax": "[-NoControlledFolders]",
"name": "Defender - Enable",
"description": "Enables Windows Defender and sets preferences",
"shell": "powershell",
@@ -368,6 +483,7 @@
"submittedBy": "https://github.com/dinger1986",
"name": "Defender - Status Report",
"description": "This will check for Malware and Antispyware within the last 24 hours and display, otherwise will report as Healthy. Command Parameter: (number) if provided will check that number of days back in the log.",
"syntax": "[<int>]",
"shell": "powershell",
"category": "TRMM (Win):Security>Antivirus"
},
@@ -403,6 +519,7 @@
"filename": "Win_Display_Message_To_User.ps1",
"submittedBy": "https://github.com/bradhawkins85",
"name": "Message Popup To User",
"syntax": "<string>",
"description": "Displays a popup message to the currently logged on user",
"shell": "powershell",
"category": "TRMM (Win):Other"
@@ -412,6 +529,7 @@
"filename": "Win_Antivirus_Verify.ps1",
"submittedBy": "https://github.com/beejayzed",
"name": "Antivirus - Verify Status",
"syntax": "[-antivirusName <string>]",
"description": "Verify and display status for all installed Antiviruses",
"shell": "powershell",
"category": "TRMM (Win):Security>Antivirus"
@@ -431,11 +549,7 @@
"submittedBy": "https://github.com/silversword411",
"name": "Chocolatey - Install, Uninstall and Upgrade Software",
"description": "This script installs, uninstalls and updates software using Chocolatey with logic to slow tasks to minimize hitting community limits. Mode install/uninstall/upgrade Hosts x",
"args": [
"-$PackageName <string>",
"[-Hosts <string>]",
"[-mode {(install) | update | uninstall}]"
],
"syntax": "-$PackageName <string>\n[-Hosts <string>]\n[-mode {(install) | update | uninstall}]",
"shell": "powershell",
"category": "TRMM (Win):3rd Party Software>Chocolatey",
"default_timeout": "600"
@@ -460,10 +574,11 @@
},
{
"guid": "71090fc4-faa6-460b-adb0-95d7863544e1",
"filename": "Win_Check_Events_for_Bluescreens.ps1",
"submittedBy": "https://github.com/dinger1986",
"filename": "Win_Bluescreen_Report.ps1",
"submittedBy": "https://github.com/bbrendon",
"name": "Event Viewer - Bluescreen Notification",
"description": "Event Viewer Monitor - Notify Bluescreen events on your system",
"syntax": "[<int>]",
"shell": "powershell",
"category": "TRMM (Win):Monitoring"
},
@@ -472,7 +587,8 @@
"filename": "Win_Local_User_Created_Monitor.ps1",
"submittedBy": "https://github.com/dinger1986",
"name": "Event Viewer - New User Notification",
"description": "Event Viewer Monitor - Notify when new Local user is created",
"description": "Event Viewer Monitor - Notify when new Local user is created. If parameter provided will search back that number of days",
"syntax": "[<int>]",
"shell": "powershell",
"category": "TRMM (Win):Monitoring"
},
@@ -482,6 +598,7 @@
"submittedBy": "https://github.com/dinger1986",
"name": "Event Viewer - Task Scheduler New Item Notification",
"description": "Event Viewer Monitor - Notify when new Task Scheduler item is created",
"syntax": "[<int>]",
"shell": "powershell",
"category": "TRMM (Win):Monitoring"
},
@@ -500,12 +617,7 @@
"submittedBy": "https://github.com/silversword411",
"name": "Rename Computer",
"description": "Rename computer. First parameter will be new PC name. 2nd parameter if yes will auto-reboot machine",
"args": [
"-NewName <string>",
"[-Username <string>]",
"[-Password <string>]",
"[-Restart]"
],
"syntax": "-NewName <string>\n[-Username <string>]\n[-Password <string>]\n[-Restart]",
"shell": "powershell",
"category": "TRMM (Win):Other",
"default_timeout": 30
@@ -516,9 +628,7 @@
"submittedBy": "https://github.com/tremor021",
"name": "Power - Restart or Shutdown PC",
"description": "Restart PC. Add parameter: shutdown if you want to shutdown computer",
"args": [
"[shutdown]"
],
"syntax": "[shutdown]",
"shell": "powershell",
"category": "TRMM (Win):Updates"
},
@@ -697,6 +807,15 @@
"shell": "powershell",
"category": "TRMM (Win):Security"
},
{
"guid": "43a3206d-f1cb-44ef-8405-aae4d33a0bad",
"filename": "Win_Security_Audit.ps1",
"submittedBy": "theinterwebs",
"name": "Windows Security - Security Audit",
"description": "Runs an Audit on many components of windows to check for security issues",
"shell": "powershell",
"category": "TRMM (Win):Security"
},
{
"guid": "7ea6a11a-05c0-4151-b5c1-cb8af029299f",
"filename": "Win_AzureAD_Check_Connection_Status.ps1",
@@ -757,13 +876,17 @@
"submittedBy": "https://github.com/brodur",
"name": "User - Create Local",
"description": "Create a local user. Parameters are: username, password and optional: description, fullname, group (adds to Users if not specified)",
"args": [
"-username <string>",
"-password <string>",
"[-description <string>]",
"[-fullname <string>]",
"[-group <string>]"
],
"syntax": "-username <string>\n-password <string>\n[-description <string>]\n[-fullname <string>]\n[-group <string>]",
"shell": "powershell",
"category": "TRMM (Win):User Management"
},
{
"guid": "6e27d5341-88fa-4c2f-9c91-c3aeb1740e85",
"filename": "Win_User_EnableDisable.ps1",
"submittedBy": "https://github.com/silversword411",
"name": "User - Enable or disable a user",
"description": "Used to enable or disable local user",
"syntax": "-Name <string>\n-Enabled { yes | no }",
"shell": "powershell",
"category": "TRMM (Win):User Management"
},
@@ -810,6 +933,7 @@
"submittedBy": "https://github.com/tremor021",
"name": "EXAMPLE File Copying using powershell",
"description": "Reference Script: Will need manual tweaking, for copying files/folders from paths/websites to local",
"syntax": "-source <string>\n-destination <string>\n[-recursive {True | False}]",
"shell": "powershell",
"category": "TRMM (Win):Misc>Reference",
"default_timeout": "1"
@@ -829,6 +953,7 @@
"filename": "Win_AD_Join_Computer.ps1",
"submittedBy": "https://github.com/rfost52",
"name": "AD - Join Computer to Domain",
"syntax": "-domain <string>\n-password <string>\n-UserAccount ADMINaccount\n[-OUPath <OU=testOU,DC=test,DC=local>]",
"description": "Join computer to a domain in Active Directory",
"shell": "powershell",
"category": "TRMM (Win):Active Directory",
@@ -839,6 +964,7 @@
"filename": "Win_Collect_System_Report_And_Email.ps1",
"submittedBy": "https://github.com/rfost52",
"name": "Collect System Report and Email",
"syntax": "-agentname <string>\n-file <string enter file name with the extension .HTM or .HTML>\n-fromaddress <string>\n-toaddress <string>\n-smtpserver <string>\n-password <string>\n-port <int 587 is the standard port for sending mail over TLS>",
"description": "Generates a system report in HTML format, then emails it",
"shell": "powershell",
"category": "TRMM (Win):Other",

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.6 on 2021-11-13 16:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('scripts', '0012_auto_20210917_1954'),
]
operations = [
migrations.AddField(
model_name='script',
name='syntax',
field=models.TextField(blank=True, null=True),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.6 on 2021-11-19 15:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('scripts', '0013_script_syntax'),
]
operations = [
migrations.AlterField(
model_name='script',
name='filename',
field=models.CharField(blank=True, max_length=255, null=True),
),
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 3.2.9 on 2021-11-28 16:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('scripts', '0014_alter_script_filename'),
]
operations = [
migrations.AddField(
model_name='script',
name='script_body',
field=models.TextField(blank=True, default=''),
),
migrations.AddField(
model_name='script',
name='script_hash',
field=models.CharField(blank=True, max_length=100, null=True),
),
migrations.AlterField(
model_name='script',
name='code_base64',
field=models.TextField(blank=True, default=''),
),
]

View File

@@ -1,5 +1,6 @@
import base64
import re
import hmac
import hashlib
from typing import List
from django.contrib.postgres.fields import ArrayField
@@ -24,7 +25,7 @@ class Script(BaseAuditModel):
guid = models.CharField(max_length=64, null=True, blank=True)
name = models.CharField(max_length=255)
description = models.TextField(null=True, blank=True, default="")
filename = models.CharField(max_length=255) # deprecated
filename = models.CharField(max_length=255, null=True, blank=True)
shell = models.CharField(
max_length=100, choices=SCRIPT_SHELLS, default="powershell"
)
@@ -37,9 +38,12 @@ class Script(BaseAuditModel):
blank=True,
default=list,
)
syntax = TextField(null=True, blank=True)
favorite = models.BooleanField(default=False)
category = models.CharField(max_length=100, null=True, blank=True)
code_base64 = models.TextField(null=True, blank=True, default="")
script_body = models.TextField(blank=True, default="")
script_hash = models.CharField(max_length=100, null=True, blank=True)
code_base64 = models.TextField(blank=True, default="") # deprecated
default_timeout = models.PositiveIntegerField(default=90)
def __str__(self):
@@ -47,12 +51,7 @@ class Script(BaseAuditModel):
@property
def code_no_snippets(self):
if self.code_base64:
return base64.b64decode(self.code_base64.encode("ascii", "ignore")).decode(
"ascii", "ignore"
)
else:
return ""
return self.script_body if self.script_body else ""
@property
def code(self):
@@ -77,6 +76,12 @@ class Script(BaseAuditModel):
else:
return code
def hash_script_body(self):
from django.conf import settings
msg = self.code.encode(errors="ignore")
return hmac.new(settings.SECRET_KEY.encode(), msg, hashlib.sha256).hexdigest()
@classmethod
def load_community_scripts(cls):
import json
@@ -99,6 +104,9 @@ class Script(BaseAuditModel):
) as f:
info = json.load(f)
# used to remove scripts from DB that are removed from the json file and file system
community_scripts_processed = [] # list of script guids
for script in info:
if os.path.exists(os.path.join(scripts_dir, script["filename"])):
s = cls.objects.filter(script_type="builtin", guid=script["guid"])
@@ -115,83 +123,36 @@ class Script(BaseAuditModel):
args = script["args"] if "args" in script.keys() else []
syntax = script["syntax"] if "syntax" in script.keys() else ""
# if community script exists update it
if s.exists():
i = s.first()
i.name = script["name"] # type: ignore
i.description = script["description"] # type: ignore
i.category = category # type: ignore
i.shell = script["shell"] # type: ignore
i.default_timeout = default_timeout # type: ignore
i.args = args # type: ignore
i: Script = s.get()
i.name = script["name"]
i.description = script["description"]
i.category = category
i.shell = script["shell"]
i.default_timeout = default_timeout
i.args = args
i.syntax = syntax
i.filename = script["filename"]
with open(os.path.join(scripts_dir, script["filename"]), "rb") as f:
script_bytes = (
f.read().decode("utf-8").encode("ascii", "ignore")
)
i.code_base64 = base64.b64encode(script_bytes).decode("ascii") # type: ignore
i.script_body = f.read().decode("utf-8")
# i.hash_script_body()
i.save()
i.save( # type: ignore
update_fields=[
"name",
"description",
"category",
"default_timeout",
"code_base64",
"shell",
"args",
]
)
# check if script was added without a guid
elif cls.objects.filter(
script_type="builtin", name=script["name"]
).exists():
s = cls.objects.get(script_type="builtin", name=script["name"])
if not s.guid:
print(f"Updating GUID for: {script['name']}")
s.guid = script["guid"]
s.name = script["name"]
s.description = script["description"]
s.category = category
s.shell = script["shell"]
s.default_timeout = default_timeout
s.args = args
with open(
os.path.join(scripts_dir, script["filename"]), "rb"
) as f:
script_bytes = (
f.read().decode("utf-8").encode("ascii", "ignore")
)
s.code_base64 = base64.b64encode(script_bytes).decode(
"ascii"
)
s.save(
update_fields=[
"guid",
"name",
"description",
"category",
"default_timeout",
"code_base64",
"shell",
"args",
]
)
community_scripts_processed.append(i.guid)
# doesn't exist in database so create it
else:
print(f"Adding new community script: {script['name']}")
with open(os.path.join(scripts_dir, script["filename"]), "rb") as f:
script_bytes = (
f.read().decode("utf-8").encode("ascii", "ignore")
)
code_base64 = base64.b64encode(script_bytes).decode("ascii")
script_body = f.read().decode("utf-8")
cls(
code_base64=code_base64,
new_script: Script = cls(
script_body=script_body,
guid=script["guid"],
name=script["name"],
description=script["description"],
@@ -200,10 +161,24 @@ class Script(BaseAuditModel):
category=category,
default_timeout=default_timeout,
args=args,
).save()
filename=script["filename"],
syntax=syntax,
)
# new_script.hash_script_body() # also saves script
new_script.save()
# delete community scripts that had their name changed
cls.objects.filter(script_type="builtin", guid=None).delete()
community_scripts_processed.append(new_script.guid)
# check for community scripts that were deleted from json and scripts folder
count, _ = (
Script.objects.filter(script_type="builtin")
.exclude(guid__in=community_scripts_processed)
.delete()
)
if count:
print(
f"Removing {count} community scripts that was removed from source repo"
)
@staticmethod
def serialize(script):

View File

@@ -16,10 +16,14 @@ class ScriptTableSerializer(ModelSerializer):
"category",
"favorite",
"default_timeout",
"syntax",
"filename",
]
class ScriptSerializer(ModelSerializer):
script_hash = ReadOnlyField()
class Meta:
model = Script
fields = [
@@ -30,17 +34,21 @@ class ScriptSerializer(ModelSerializer):
"args",
"category",
"favorite",
"code_base64",
"script_body",
"script_hash",
"default_timeout",
"syntax",
"filename",
]
class ScriptCheckSerializer(ModelSerializer):
code = ReadOnlyField()
script_hash = ReadOnlyField
class Meta:
model = Script
fields = ["code", "shell"]
fields = ["code", "shell", "script_hash"]
class ScriptSnippetSerializer(ModelSerializer):

View File

@@ -1,6 +1,5 @@
import asyncio
from packaging import version as pyver
from agents.models import Agent, AgentHistory
from scripts.models import Script
@@ -20,14 +19,13 @@ def handle_bulk_command_task(
},
}
for agent in Agent.objects.filter(pk__in=agentpks):
if pyver.parse(agent.version) >= pyver.parse("1.6.0"):
hist = AgentHistory.objects.create(
agent=agent,
type="cmd_run",
command=cmd,
username=username,
)
nats_data["id"] = hist.pk
hist = AgentHistory.objects.create(
agent=agent,
type="cmd_run",
command=cmd,
username=username,
)
nats_data["id"] = hist.pk
asyncio.run(agent.nats_cmd(nats_data, wait=False))
@@ -36,15 +34,12 @@ def handle_bulk_command_task(
def handle_bulk_script_task(scriptpk, agentpks, args, timeout, username) -> None:
script = Script.objects.get(pk=scriptpk)
for agent in Agent.objects.filter(pk__in=agentpks):
history_pk = 0
if pyver.parse(agent.version) >= pyver.parse("1.6.0"):
hist = AgentHistory.objects.create(
agent=agent,
type="script_run",
script=script,
username=username,
)
history_pk = hist.pk
agent.run_script(
scriptpk=script.pk, args=args, timeout=timeout, history_pk=history_pk
hist = AgentHistory.objects.create(
agent=agent,
type="script_run",
script=script,
username=username,
)
agent.run_script(
scriptpk=script.pk, args=args, timeout=timeout, history_pk=hist.pk
)

View File

@@ -1,8 +1,12 @@
import json
import os
import hmac
import hashlib
from pathlib import Path
from unittest.mock import patch
from django.test import override_settings
from django.conf import settings
from model_bakery import baker
from tacticalrmm.test import TacticalTestCase
@@ -31,6 +35,7 @@ class TestScriptViews(TacticalTestCase):
self.check_not_authenticated("get", url)
@override_settings(SECRET_KEY="Test Secret Key")
def test_add_script(self):
url = f"/scripts/"
@@ -39,7 +44,7 @@ class TestScriptViews(TacticalTestCase):
"description": "Description",
"shell": "powershell",
"category": "New",
"code_base64": "VGVzdA==", # Test
"script_body": "Test Script",
"default_timeout": 99,
"args": ["hello", "world", r"{{agent.public_ip}}"],
"favorite": False,
@@ -48,11 +53,18 @@ class TestScriptViews(TacticalTestCase):
# test without file upload
resp = self.client.post(url, data, format="json")
self.assertEqual(resp.status_code, 200)
self.assertTrue(Script.objects.filter(name="Name").exists())
self.assertEqual(Script.objects.get(name="Name").code, "Test")
new_script = Script.objects.filter(name="Name").get()
self.assertTrue(new_script)
# correct_hash = hmac.new(
# settings.SECRET_KEY.encode(), data["script_body"].encode(), hashlib.sha256
# ).hexdigest()
# self.assertEqual(new_script.script_hash, correct_hash)
self.check_not_authenticated("post", url)
@override_settings(SECRET_KEY="Test Secret Key")
def test_modify_script(self):
# test a call where script doesn't exist
resp = self.client.put("/scripts/500/", format="json")
@@ -66,7 +78,7 @@ class TestScriptViews(TacticalTestCase):
"name": script.name,
"description": "Description Change",
"shell": script.shell,
"code_base64": "VGVzdA==", # Test
"script_body": "Test Script Body", # Test
"default_timeout": 13344556,
}
@@ -75,14 +87,17 @@ class TestScriptViews(TacticalTestCase):
self.assertEqual(resp.status_code, 200)
script = Script.objects.get(pk=script.pk)
self.assertEquals(script.description, "Description Change")
self.assertEquals(script.code, "Test")
# correct_hash = hmac.new(
# settings.SECRET_KEY.encode(), data["script_body"].encode(), hashlib.sha256
# ).hexdigest()
# self.assertEqual(script.script_hash, correct_hash)
# test edit a builtin script
data = {
"name": "New Name",
"description": "New Desc",
"code_base64": "VGVzdA==",
"script_body": "aasdfdsf",
} # Test
builtin_script = baker.make_recipe("scripts.script", script_type="builtin")
@@ -94,7 +109,7 @@ class TestScriptViews(TacticalTestCase):
"description": "Description Change",
"shell": script.shell,
"favorite": True,
"code_base64": "VGVzdA==", # Test
"script_body": "Test Script Body", # Test
"default_timeout": 54345,
}
# test marking a builtin script as favorite
@@ -166,29 +181,33 @@ class TestScriptViews(TacticalTestCase):
# test powershell file
script = baker.make(
"scripts.Script", code_base64="VGVzdA==", shell="powershell"
"scripts.Script", script_body="Test Script Body", shell="powershell"
)
url = f"/scripts/{script.pk}/download/" # type: ignore
resp = self.client.get(url, format="json")
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.data, {"filename": f"{script.name}.ps1", "code": "Test"}) # type: ignore
self.assertEqual(resp.data, {"filename": f"{script.name}.ps1", "code": "Test Script Body"}) # type: ignore
# test batch file
script = baker.make("scripts.Script", code_base64="VGVzdA==", shell="cmd")
script = baker.make(
"scripts.Script", script_body="Test Script Body", shell="cmd"
)
url = f"/scripts/{script.pk}/download/" # type: ignore
resp = self.client.get(url, format="json")
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.data, {"filename": f"{script.name}.bat", "code": "Test"}) # type: ignore
self.assertEqual(resp.data, {"filename": f"{script.name}.bat", "code": "Test Script Body"}) # type: ignore
# test python file
script = baker.make("scripts.Script", code_base64="VGVzdA==", shell="python")
script = baker.make(
"scripts.Script", script_body="Test Script Body", shell="python"
)
url = f"/scripts/{script.pk}/download/" # type: ignore
resp = self.client.get(url, format="json")
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.data, {"filename": f"{script.name}.py", "code": "Test"}) # type: ignore
self.assertEqual(resp.data, {"filename": f"{script.name}.py", "code": "Test Script Body"}) # type: ignore
self.check_not_authenticated("get", url)

View File

@@ -1,4 +1,3 @@
import base64
import asyncio
from django.shortcuts import get_object_or_404
@@ -37,6 +36,8 @@ class GetAddScripts(APIView):
serializer.is_valid(raise_exception=True)
obj = serializer.save()
# obj.hash_script_body()
return Response(f"{obj.name} was added!")
@@ -64,6 +65,8 @@ class GetUpdateDeleteScript(APIView):
serializer.is_valid(raise_exception=True)
obj = serializer.save()
# obj.hash_script_body()
return Response(f"{obj.name} was edited!")
def delete(self, request, pk):

View File

@@ -1,8 +1,8 @@
import asyncio
from agents.models import Agent
from checks.models import Check
from django.shortcuts import get_object_or_404
from django.conf import settings
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
@@ -15,6 +15,11 @@ class GetServices(APIView):
permission_classes = [IsAuthenticated, WinSvcsPerms]
def get(self, request, agent_id):
if getattr(settings, "DEMO", False):
from tacticalrmm.demo_views import demo_get_services
return demo_get_services()
agent = get_object_or_404(Agent, agent_id=agent_id)
r = asyncio.run(agent.nats_cmd(data={"func": "winservices"}, timeout=10))

View File

@@ -2,7 +2,6 @@ import asyncio
from typing import Any
from django.shortcuts import get_object_or_404
from packaging import version as pyver
from rest_framework.decorators import api_view
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
@@ -10,7 +9,7 @@ from rest_framework.views import APIView
from agents.models import Agent
from logs.models import PendingAction
from tacticalrmm.utils import filter_software, notify_error
from tacticalrmm.utils import notify_error
from .models import ChocoSoftware, InstalledSoftware
from .permissions import SoftwarePerms
@@ -42,9 +41,6 @@ class GetSoftware(APIView):
# software install
def post(self, request, agent_id):
agent = get_object_or_404(Agent, agent_id=agent_id)
if pyver.parse(agent.version) < pyver.parse("1.4.8"):
return notify_error("Requires agent v1.4.8")
name = request.data["name"]
action = PendingAction.objects.create(
@@ -76,13 +72,11 @@ class GetSoftware(APIView):
if r == "timeout" or r == "natsdown":
return notify_error("Unable to contact the agent")
sw = filter_software(r)
if not InstalledSoftware.objects.filter(agent=agent).exists():
InstalledSoftware(agent=agent, software=sw).save()
InstalledSoftware(agent=agent, software=r).save()
else:
s = agent.installedsoftware_set.first() # type: ignore
s.software = sw
s.software = r
s.save(update_fields=["software"])
return Response("ok")

View File

@@ -20,8 +20,9 @@ app.accept_content = ["application/json"] # type: ignore
app.result_serializer = "json" # type: ignore
app.task_serializer = "json" # type: ignore
app.conf.task_track_started = True
app.autodiscover_tasks()
app.conf.worker_proc_alive_timeout = 30
app.conf.worker_max_tasks_per_child = 2
app.autodiscover_tasks()
app.conf.beat_schedule = {
"auto-approve-win-updates": {
@@ -36,18 +37,6 @@ app.conf.beat_schedule = {
"task": "agents.tasks.auto_self_agent_update_task",
"schedule": crontab(minute=35, hour="*"),
},
"handle-agents": {
"task": "agents.tasks.handle_agents_task",
"schedule": crontab(minute="*"),
},
"get-agentinfo": {
"task": "agents.tasks.agent_getinfo_task",
"schedule": crontab(minute="*"),
},
"get-wmi": {
"task": "agents.tasks.get_wmi_task",
"schedule": crontab(minute=18, hour="*/5"),
},
}
@@ -56,15 +45,14 @@ def debug_task(self):
print("Request: {0!r}".format(self.request))
@app.on_after_finalize.connect
@app.on_after_finalize.connect # type: ignore
def setup_periodic_tasks(sender, **kwargs):
from agents.tasks import agent_outages_task, agent_checkin_task
from agents.tasks import agent_outages_task
from alerts.tasks import unsnooze_alerts
from core.tasks import core_maintenance_tasks, cache_db_fields_task
sender.add_periodic_task(45.0, agent_checkin_task.s())
sender.add_periodic_task(60.0, agent_outages_task.s())
sender.add_periodic_task(60.0 * 30, core_maintenance_tasks.s())
sender.add_periodic_task(60.0 * 60, unsnooze_alerts.s())
sender.add_periodic_task(90.0, cache_db_fields_task.s())
sender.add_periodic_task(85.0, cache_db_fields_task.s())

View File

@@ -0,0 +1,539 @@
disks = [
[
{
"free": "343.3G",
"used": "121.9G",
"total": "465.3G",
"device": "C:",
"fstype": "NTFS",
"percent": 26,
},
{
"free": "745.2G",
"used": "1.1T",
"total": "1.8T",
"device": "D:",
"fstype": "NTFS",
"percent": 59,
},
{
"free": "1.2T",
"used": "669.7G",
"total": "1.8T",
"device": "F:",
"fstype": "NTFS",
"percent": 36,
},
],
[
{
"free": "516.7G",
"used": "413.5G",
"total": "930.2G",
"device": "C:",
"fstype": "NTFS",
"percent": 44,
}
],
[
{
"free": "346.5G",
"used": "129.1G",
"total": "475.6G",
"device": "C:",
"fstype": "NTFS",
"percent": 27,
}
],
[
{
"free": "84.2G",
"used": "34.4G",
"total": "118.6G",
"device": "C:",
"fstype": "NTFS",
"percent": 29,
}
],
]
ping_success_output = """
Pinging 8.8.8.8 with 32 bytes of data:
Reply from 8.8.8.8: bytes=32 time=28ms TTL=116
Reply from 8.8.8.8: bytes=32 time=26ms TTL=116
Reply from 8.8.8.8: bytes=32 time=29ms TTL=116
Reply from 8.8.8.8: bytes=32 time=23ms TTL=116
Ping statistics for 8.8.8.8:
Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 23ms, Maximum = 29ms, Average = 26ms
"""
ping_fail_output = """
Pinging 10.42.33.2 with 32 bytes of data:
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Ping statistics for 10.42.33.2:
Packets: Sent = 4, Received = 0, Lost = 4 (100% loss),
"""
spooler_stdout = """
SERVICE_NAME: spooler
TYPE : 110 WIN32_OWN_PROCESS (interactive)
STATE : 3 STOP_PENDING
(NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
WIN32_EXIT_CODE : 0 (0x0)
SERVICE_EXIT_CODE : 0 (0x0)
CHECKPOINT : 0x0
WAIT_HINT : 0x0
Deleted file - C:\Windows\System32\spool\printers\FP00004.SHD
Deleted file - C:\Windows\System32\spool\printers\FP00004.SPL
SERVICE_NAME: spooler
TYPE : 110 WIN32_OWN_PROCESS (interactive)
STATE : 2 START_PENDING
(NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
WIN32_EXIT_CODE : 0 (0x0)
SERVICE_EXIT_CODE : 0 (0x0)
CHECKPOINT : 0x0
WAIT_HINT : 0x7d0
PID : 10536
FLAGS :
"""
temp_dir_stdout = """
Total files: 427
{'name': '2E71.tmp', 'size': 7430272, 'mtime': 1581925416.2497344}
{'name': 'AdobeARM.log', 'size': 29451, 'mtime': 1594655619.9011872}
{'name': 'adobegc.log', 'size': 10231328, 'mtime': 1595040481.91346}
{'name': 'adobegc_a00168', 'size': 827, 'mtime': 1587681946.9771478}
{'name': 'adobegc_a00736', 'size': 827, 'mtime': 1588706044.6594567}
{'name': 'adobegc_a01612', 'size': 827, 'mtime': 1580168032.7042644}
{'name': 'adobegc_a01872', 'size': 827, 'mtime': 1588695409.1667633}
{'name': 'adobegc_a02040', 'size': 827, 'mtime': 1586472391.868406}
{'name': 'adobegc_a02076', 'size': 827, 'mtime': 1580250789.654343}
{'name': 'adobegc_a02316', 'size': 827, 'mtime': 1584469722.280189}
{'name': 'adobegc_a02840', 'size': 827, 'mtime': 1580168195.0954776}
{'name': 'adobegc_a02844', 'size': 827, 'mtime': 1588704553.4443035}
{'name': 'adobegc_a02940', 'size': 827, 'mtime': 1588705125.4622736}
{'name': 'adobegc_a03388', 'size': 827, 'mtime': 1588694931.7341642}
{'name': 'adobegc_a03444', 'size': 827, 'mtime': 1588694575.377482}
{'name': 'adobegc_a03468', 'size': 827, 'mtime': 1588705816.5495117}
{'name': 'adobegc_a03516', 'size': 827, 'mtime': 1588695236.1638494}
{'name': 'adobegc_a03660', 'size': 827, 'mtime': 1588694714.0769584}
{'name': 'adobegc_a03668', 'size': 827, 'mtime': 1588791976.2615259}
{'name': 'adobegc_a03984', 'size': 827, 'mtime': 1588708060.4916122}
{'name': 'adobegc_a04244', 'size': 827, 'mtime': 1588882348.195425}
{'name': 'adobegc_a04296', 'size': 827, 'mtime': 1587595547.000954}
{'name': 'adobegc_a04400', 'size': 827, 'mtime': 1588698785.5022683}
{'name': 'adobegc_a04476', 'size': 827, 'mtime': 1588696181.497377}
{'name': 'adobegc_a04672', 'size': 827, 'mtime': 1588707309.2342112}
{'name': 'adobegc_a05072', 'size': 827, 'mtime': 1588718744.760823}
{'name': 'adobegc_a05308', 'size': 827, 'mtime': 1588884352.0702107}
{'name': 'adobegc_a05372', 'size': 827, 'mtime': 1587571313.9485312}
{'name': 'adobegc_a06196', 'size': 826, 'mtime': 1594654959.318391}
{'name': 'adobegc_a07432', 'size': 827, 'mtime': 1588887412.235366}
{'name': 'adobegc_a07592', 'size': 827, 'mtime': 1587768346.7856867}
{'name': 'adobegc_a08336', 'size': 827, 'mtime': 1580251587.8173583}
{'name': 'adobegc_a08540', 'size': 826, 'mtime': 1590517886.4135766}
{'name': 'adobegc_a08676', 'size': 827, 'mtime': 1588796865.261678}
{'name': 'adobegc_a08788', 'size': 827, 'mtime': 1586385998.4148164}
{'name': 'adobegc_a09164', 'size': 827, 'mtime': 1588882638.920801}
{'name': 'adobegc_a10672', 'size': 827, 'mtime': 1580142397.240663}
{'name': 'adobegc_a11260', 'size': 827, 'mtime': 1588791820.5066414}
{'name': 'adobegc_a12180', 'size': 827, 'mtime': 1580146831.0441327}
{'name': 'adobegc_a14468', 'size': 827, 'mtime': 1585674106.878755}
{'name': 'adobegc_a14596', 'size': 827, 'mtime': 1580510788.5562158}
{'name': 'adobegc_a15124', 'size': 826, 'mtime': 1590523889.367007}
{'name': 'adobegc_a15936', 'size': 827, 'mtime': 1580256796.572934}
{'name': 'adobegc_a15992', 'size': 826, 'mtime': 1594664396.6619377}
{'name': 'adobegc_a16976', 'size': 827, 'mtime': 1585674384.4933422}
{'name': 'adobegc_a18972', 'size': 826, 'mtime': 1594748694.4924471}
{'name': 'adobegc_a19836', 'size': 827, 'mtime': 1588880974.3856514}
{'name': 'adobegc_a20168', 'size': 827, 'mtime': 1580256300.931633}
{'name': 'adobegc_a20424', 'size': 826, 'mtime': 1590619548.096738}
{'name': 'adobegc_a20476', 'size': 827, 'mtime': 1580241090.30506}
{'name': 'adobegc_a20696', 'size': 827, 'mtime': 1588883054.266526}
{'name': 'adobegc_a21160', 'size': 827, 'mtime': 1585867545.8835862}
{'name': 'adobegc_a21600', 'size': 827, 'mtime': 1584546053.6350517}
{'name': 'adobegc_a21604', 'size': 827, 'mtime': 1585781145.016732}
{'name': 'adobegc_a23208', 'size': 826, 'mtime': 1594766767.8597474}
{'name': 'adobegc_a23792', 'size': 827, 'mtime': 1587748006.602304}
{'name': 'adobegc_a24996', 'size': 827, 'mtime': 1580337748.2107458}
{'name': 'adobegc_a25280', 'size': 827, 'mtime': 1589561457.17108}
{'name': 'adobegc_a25716', 'size': 827, 'mtime': 1586558746.818827}
{'name': 'adobegc_a26788', 'size': 827, 'mtime': 1589317959.1261017}
{'name': 'adobegc_a29788', 'size': 826, 'mtime': 1594853168.0936923}
{'name': 'adobegc_a30772', 'size': 827, 'mtime': 1586645146.8381186}
{'name': 'adobegc_a30868', 'size': 826, 'mtime': 1590705947.4294834}
{'name': 'adobegc_a33340', 'size': 827, 'mtime': 1580424388.6278617}
{'name': 'adobegc_a34072', 'size': 826, 'mtime': 1591036884.930815}
{'name': 'adobegc_a34916', 'size': 827, 'mtime': 1589063225.3951604}
{'name': 'adobegc_a36312', 'size': 826, 'mtime': 1590792347.1066074}
{'name': 'adobegc_a36732', 'size': 827, 'mtime': 1587941146.7667546}
{'name': 'adobegc_a37684', 'size': 826, 'mtime': 1591051545.0491257}
{'name': 'adobegc_a38820', 'size': 826, 'mtime': 1594939568.850206}
{'name': 'adobegc_a39800', 'size': 827, 'mtime': 1586731547.200965}
{'name': 'adobegc_a39968', 'size': 827, 'mtime': 1585953945.0148494}
{'name': 'adobegc_a40276', 'size': 827, 'mtime': 1580774658.3211977}
{'name': 'adobegc_a40312', 'size': 827, 'mtime': 1589237146.946895}
{'name': 'adobegc_a40988', 'size': 827, 'mtime': 1586817946.949773}
{'name': 'adobegc_a40992', 'size': 827, 'mtime': 1580770793.4948478}
{'name': 'adobegc_a41180', 'size': 827, 'mtime': 1588027546.7357743}
{'name': 'adobegc_a41188', 'size': 826, 'mtime': 1595009475.295114}
{'name': 'adobegc_a41640', 'size': 826, 'mtime': 1590878747.5881732}
{'name': 'adobegc_a42100', 'size': 827, 'mtime': 1589150748.9527063}
{'name': 'adobegc_a43012', 'size': 827, 'mtime': 1580683588.5658195}
{'name': 'adobegc_a44676', 'size': 827, 'mtime': 1580597188.6451297}
{'name': 'adobegc_a45184', 'size': 827, 'mtime': 1586904346.5828853}
{'name': 'adobegc_a45308', 'size': 826, 'mtime': 1595025969.4777381}
{'name': 'adobegc_a46804', 'size': 827, 'mtime': 1580772007.5569854}
{'name': 'adobegc_a47368', 'size': 827, 'mtime': 1588100330.3886814}
{'name': 'adobegc_a47428', 'size': 827, 'mtime': 1589307202.4241476}
{'name': 'adobegc_a48120', 'size': 827, 'mtime': 1587061429.3050117}
{'name': 'adobegc_a48264', 'size': 827, 'mtime': 1586040345.9605994}
{'name': 'adobegc_a49348', 'size': 827, 'mtime': 1589308572.5917764}
{'name': 'adobegc_a50068', 'size': 827, 'mtime': 1589823616.2651317}
{'name': 'adobegc_a50512', 'size': 827, 'mtime': 1588113946.9230535}
{'name': 'adobegc_a54396', 'size': 826, 'mtime': 1590965147.3472395}
{'name': 'adobegc_a55764', 'size': 827, 'mtime': 1586126745.5002806}
{'name': 'adobegc_a56868', 'size': 827, 'mtime': 1584988994.5648835}
{'name': 'adobegc_a56920', 'size': 827, 'mtime': 1589826940.2840052}
{'name': 'adobegc_a58060', 'size': 827, 'mtime': 1588200346.9590664}
{'name': 'adobegc_a58664', 'size': 827, 'mtime': 1580772553.408082}
{'name': 'adobegc_a58836', 'size': 827, 'mtime': 1586213145.9856122}
{'name': 'adobegc_a58952', 'size': 827, 'mtime': 1580769957.2542257}
{'name': 'adobegc_a59448', 'size': 827, 'mtime': 1586191309.3788278}
{'name': 'adobegc_a59920', 'size': 827, 'mtime': 1580837382.8384278}
{'name': 'adobegc_a60092', 'size': 827, 'mtime': 1589820894.3119876}
{'name': 'adobegc_a60188', 'size': 827, 'mtime': 1580773319.7630682}
{'name': 'adobegc_a60376', 'size': 827, 'mtime': 1584984234.995152}
{'name': 'adobegc_a60836', 'size': 827, 'mtime': 1586990747.0581498}
{'name': 'adobegc_a61768', 'size': 826, 'mtime': 1591035116.5898964}
{'name': 'adobegc_a62200', 'size': 827, 'mtime': 1586195417.8275757}
{'name': 'adobegc_a62432', 'size': 827, 'mtime': 1580942790.5409286}
{'name': 'adobegc_a65288', 'size': 827, 'mtime': 1588373147.0190327}
{'name': 'adobegc_a65332', 'size': 827, 'mtime': 1580838023.0994027}
{'name': 'adobegc_a65672', 'size': 826, 'mtime': 1591133604.032657}
{'name': 'adobegc_a66164', 'size': 827, 'mtime': 1580837511.0639248}
{'name': 'adobegc_a66532', 'size': 827, 'mtime': 1587077146.9995973}
{'name': 'adobegc_a66744', 'size': 827, 'mtime': 1587061281.624075}
{'name': 'adobegc_a68000', 'size': 826, 'mtime': 1591137947.4248047}
{'name': 'adobegc_a68072', 'size': 827, 'mtime': 1589928347.070936}
{'name': 'adobegc_a68720', 'size': 827, 'mtime': 1581029189.2618403}
{'name': 'adobegc_a68848', 'size': 827, 'mtime': 1587163546.6515636}
{'name': 'adobegc_a69732', 'size': 827, 'mtime': 1580930519.3353608}
{'name': 'adobegc_a70528', 'size': 827, 'mtime': 1589841947.731212}
{'name': 'adobegc_a71096', 'size': 827, 'mtime': 1580920745.8008296}
{'name': 'adobegc_a71132', 'size': 827, 'mtime': 1586299545.3437998}
{'name': 'adobegc_a71648', 'size': 827, 'mtime': 1581031075.2702963}
{'name': 'adobegc_a72972', 'size': 827, 'mtime': 1588626359.7385614}
{'name': 'adobegc_a75840', 'size': 826, 'mtime': 1591373471.8618608}
{'name': 'adobegc_a76096', 'size': 827, 'mtime': 1581030280.123038}
{'name': 'adobegc_a76636', 'size': 827, 'mtime': 1581010194.8814292}
{'name': 'adobegc_a76928', 'size': 827, 'mtime': 1586368563.2366085}
{'name': 'adobegc_a78272', 'size': 827, 'mtime': 1588459547.364358}
{'name': 'adobegc_a78448', 'size': 827, 'mtime': 1589755547.709028}
{'name': 'adobegc_a78868', 'size': 827, 'mtime': 1587249947.0512784}
{'name': 'adobegc_a79232', 'size': 827, 'mtime': 1590014747.2459671}
{'name': 'adobegc_a80708', 'size': 827, 'mtime': 1587854746.995757}
{'name': 'adobegc_a81928', 'size': 826, 'mtime': 1591387037.7909682}
{'name': 'adobegc_a82640', 'size': 827, 'mtime': 1588620563.6037686}
{'name': 'adobegc_a84680', 'size': 827, 'mtime': 1588622988.5522242}
{'name': 'adobegc_a86668', 'size': 827, 'mtime': 1588632347.3290584}
{'name': 'adobegc_a87760', 'size': 826, 'mtime': 1591389738.9057505}
{'name': 'adobegc_a87796', 'size': 827, 'mtime': 1588620113.9931662}
{'name': 'adobegc_a88000', 'size': 827, 'mtime': 1587336346.5873897}
{'name': 'adobegc_a88772', 'size': 827, 'mtime': 1588545946.2672083}
{'name': 'adobegc_a88864', 'size': 826, 'mtime': 1591388571.4809685}
{'name': 'adobegc_a90492', 'size': 826, 'mtime': 1591569945.3237975}
{'name': 'adobegc_a90536', 'size': 827, 'mtime': 1588615525.34365}
{'name': 'adobegc_a90696', 'size': 827, 'mtime': 1588618638.6518161}
{'name': 'adobegc_a92020', 'size': 827, 'mtime': 1588626079.888435}
{'name': 'adobegc_a92036', 'size': 827, 'mtime': 1588883650.2998574}
{'name': 'adobegc_a92060', 'size': 827, 'mtime': 1587422746.7982752}
{'name': 'adobegc_a92332', 'size': 827, 'mtime': 1588617429.6228204}
{'name': 'adobegc_a92708', 'size': 827, 'mtime': 1588621683.480289}
{'name': 'adobegc_a93576', 'size': 827, 'mtime': 1588611949.1138964}
{'name': 'adobegc_a93952', 'size': 826, 'mtime': 1591483547.0099566}
{'name': 'adobegc_a93968', 'size': 827, 'mtime': 1588619947.3429031}
{'name': 'adobegc_a94188', 'size': 827, 'mtime': 1588625869.6090748}
{'name': 'adobegc_a94428', 'size': 827, 'mtime': 1588625083.0555425}
{'name': 'adobegc_a94564', 'size': 827, 'mtime': 1587400776.9892576}
{'name': 'adobegc_a94620', 'size': 827, 'mtime': 1588616005.2649503}
{'name': 'adobegc_a94672', 'size': 827, 'mtime': 1588608305.686614}
{'name': 'adobegc_a95104', 'size': 827, 'mtime': 1588619862.1936185}
{'name': 'adobegc_a95268', 'size': 827, 'mtime': 1588618316.1273627}
{'name': 'adobegc_a95992', 'size': 827, 'mtime': 1588625699.327125}
{'name': 'adobegc_a96116', 'size': 827, 'mtime': 1588625465.4000483}
{'name': 'adobegc_a96140', 'size': 827, 'mtime': 1588707579.585134}
{'name': 'adobegc_a96196', 'size': 827, 'mtime': 1588616659.9653125}
{'name': 'adobegc_a96264', 'size': 827, 'mtime': 1588624388.424492}
{'name': 'adobegc_a96396', 'size': 827, 'mtime': 1588619230.3394928}
{'name': 'adobegc_a97428', 'size': 827, 'mtime': 1587509147.0930684}
{'name': 'adobegc_a97480', 'size': 827, 'mtime': 1589669147.1720312}
{'name': 'adobegc_a97532', 'size': 827, 'mtime': 1588623710.0535893}
{'name': 'adobegc_a97576', 'size': 827, 'mtime': 1588699829.405278}
{'name': 'adobegc_a97888', 'size': 827, 'mtime': 1588700236.4738493}
{'name': 'adobegc_a97936', 'size': 827, 'mtime': 1588705581.3051977}
{'name': 'adobegc_a98628', 'size': 827, 'mtime': 1588707770.5158248}
{'name': 'adobegc_a98676', 'size': 827, 'mtime': 1588707160.0849242}
{'name': 'adobegc_a99320', 'size': 827, 'mtime': 1588286747.3271153}
{'name': 'adobegc_a99416', 'size': 827, 'mtime': 1588705913.0032701}
{'name': 'adobegc_a99776', 'size': 827, 'mtime': 1588695055.6383822}
{'name': 'adobegc_a99944', 'size': 827, 'mtime': 1588700090.9956398}
{'name': 'adobegc_b00736', 'size': 827, 'mtime': 1588706066.725238}
{'name': 'adobegc_b01872', 'size': 827, 'mtime': 1588695416.625433}
{'name': 'adobegc_b02844', 'size': 827, 'mtime': 1588704612.7520032}
{'name': 'adobegc_b02940', 'size': 827, 'mtime': 1588705218.2862568}
{'name': 'adobegc_b03516', 'size': 827, 'mtime': 1588695279.1507645}
{'name': 'adobegc_b03668', 'size': 827, 'mtime': 1588791984.8225732}
{'name': 'adobegc_b03984', 'size': 827, 'mtime': 1588708170.4855063}
{'name': 'adobegc_b04400', 'size': 827, 'mtime': 1588698790.8114717}
{'name': 'adobegc_b06196', 'size': 826, 'mtime': 1594655070.3379285}
{'name': 'adobegc_b08540', 'size': 826, 'mtime': 1590517989.972172}
{'name': 'adobegc_b08676', 'size': 827, 'mtime': 1588796952.7518158}
{'name': 'adobegc_b11260', 'size': 827, 'mtime': 1588791830.28458}
{'name': 'adobegc_b12180', 'size': 827, 'mtime': 1580146854.104489}
{'name': 'adobegc_b14468', 'size': 827, 'mtime': 1585674135.6150348}
{'name': 'adobegc_b15992', 'size': 826, 'mtime': 1594664406.76352}
{'name': 'adobegc_b18972', 'size': 826, 'mtime': 1594748752.0301268}
{'name': 'adobegc_b20424', 'size': 826, 'mtime': 1590619550.6114154}
{'name': 'adobegc_b20696', 'size': 827, 'mtime': 1588883091.2836785}
{'name': 'adobegc_b25280', 'size': 827, 'mtime': 1589561471.058807}
{'name': 'adobegc_b26788', 'size': 827, 'mtime': 1589318049.2721062}
{'name': 'adobegc_b30868', 'size': 826, 'mtime': 1590705949.9086082}
{'name': 'adobegc_b34072', 'size': 826, 'mtime': 1591036916.1677504}
{'name': 'adobegc_b36312', 'size': 826, 'mtime': 1590792349.6286027}
{'name': 'adobegc_b37684', 'size': 826, 'mtime': 1591051547.7088954}
{'name': 'adobegc_b41188', 'size': 826, 'mtime': 1595009499.2530031}
{'name': 'adobegc_b41640', 'size': 826, 'mtime': 1590878750.2055979}
{'name': 'adobegc_b48120', 'size': 827, 'mtime': 1587061437.18547}
{'name': 'adobegc_b49348', 'size': 827, 'mtime': 1589308608.9336922}
{'name': 'adobegc_b50068', 'size': 827, 'mtime': 1589823624.2151668}
{'name': 'adobegc_b54396', 'size': 826, 'mtime': 1590965149.8471487}
{'name': 'adobegc_b56868', 'size': 827, 'mtime': 1584989020.8257363}
{'name': 'adobegc_b56920', 'size': 827, 'mtime': 1589826973.5304308}
{'name': 'adobegc_b58952', 'size': 827, 'mtime': 1580770043.2167466}
{'name': 'adobegc_b59448', 'size': 827, 'mtime': 1586191317.2202032}
{'name': 'adobegc_b60376', 'size': 827, 'mtime': 1584984269.807791}
{'name': 'adobegc_b68000', 'size': 826, 'mtime': 1591137949.8555748}
{'name': 'adobegc_b68072', 'size': 827, 'mtime': 1589928349.6981187}
{'name': 'adobegc_b70528', 'size': 827, 'mtime': 1589841950.8458745}
{'name': 'adobegc_b71096', 'size': 827, 'mtime': 1580920761.6914532}
{'name': 'adobegc_b72972', 'size': 827, 'mtime': 1588626390.183644}
{'name': 'adobegc_b76636', 'size': 827, 'mtime': 1581010200.9350817}
{'name': 'adobegc_b78448', 'size': 827, 'mtime': 1589755550.4021}
{'name': 'adobegc_b79232', 'size': 827, 'mtime': 1590014749.9412005}
{'name': 'adobegc_b82640', 'size': 827, 'mtime': 1588620586.923453}
{'name': 'adobegc_b84680', 'size': 827, 'mtime': 1588623002.5390074}
{'name': 'adobegc_b87796', 'size': 827, 'mtime': 1588620149.2323031}
{'name': 'adobegc_b90536', 'size': 827, 'mtime': 1588615561.6454446}
{'name': 'adobegc_b90696', 'size': 827, 'mtime': 1588618646.516128}
{'name': 'adobegc_b92020', 'size': 827, 'mtime': 1588626116.4113202}
{'name': 'adobegc_b92332', 'size': 827, 'mtime': 1588617466.6833763}
{'name': 'adobegc_b92708', 'size': 827, 'mtime': 1588621723.2322977}
{'name': 'adobegc_b93968', 'size': 827, 'mtime': 1588619970.3566632}
{'name': 'adobegc_b94188', 'size': 827, 'mtime': 1588625878.801097}
{'name': 'adobegc_b94428', 'size': 827, 'mtime': 1588625091.057683}
{'name': 'adobegc_b94564', 'size': 827, 'mtime': 1587400800.9059412}
{'name': 'adobegc_b95268', 'size': 827, 'mtime': 1588618334.0967414}
{'name': 'adobegc_b95992', 'size': 827, 'mtime': 1588625737.972303}
{'name': 'adobegc_b96116', 'size': 827, 'mtime': 1588625472.4204888}
{'name': 'adobegc_b96196', 'size': 827, 'mtime': 1588616768.8672354}
{'name': 'adobegc_b96396', 'size': 827, 'mtime': 1588619236.3330257}
{'name': 'adobegc_b97480', 'size': 827, 'mtime': 1589669149.7252228}
{'name': 'adobegc_b97532', 'size': 827, 'mtime': 1588623738.1396592}
{'name': 'adobegc_b97576', 'size': 827, 'mtime': 1588699862.141512}
{'name': 'adobegc_b97888', 'size': 827, 'mtime': 1588700318.3893816}
{'name': 'adobegc_b97936', 'size': 827, 'mtime': 1588705599.7656307}
{'name': 'adobegc_b98628', 'size': 827, 'mtime': 1588707795.8756163}
{'name': 'adobegc_b99416', 'size': 827, 'mtime': 1588705935.8479679}
{'name': 'adobegc_b99776', 'size': 827, 'mtime': 1588695083.277253}
{'name': 'adobegc_b99944', 'size': 827, 'mtime': 1588700116.4428499}
{'name': 'adobegc_c00736', 'size': 827, 'mtime': 1588706144.523482}
{'name': 'adobegc_c01872', 'size': 827, 'mtime': 1588695424.6709175}
{'name': 'adobegc_c02844', 'size': 827, 'mtime': 1588704655.3452854}
{'name': 'adobegc_c02940', 'size': 827, 'mtime': 1588705301.4180279}
{'name': 'adobegc_c03984', 'size': 827, 'mtime': 1588708227.6767087}
{'name': 'adobegc_c04400', 'size': 827, 'mtime': 1588698805.7789137}
{'name': 'adobegc_c08676', 'size': 827, 'mtime': 1588796987.8076794}
{'name': 'adobegc_c11260', 'size': 827, 'mtime': 1588791857.2477975}
{'name': 'adobegc_c12180', 'size': 827, 'mtime': 1580146876.464384}
{'name': 'adobegc_c15992', 'size': 826, 'mtime': 1594664430.9030519}
{'name': 'adobegc_c20696', 'size': 827, 'mtime': 1588883097.26129}
{'name': 'adobegc_c25280', 'size': 827, 'mtime': 1589561487.9573958}
{'name': 'adobegc_c26788', 'size': 827, 'mtime': 1589318109.375684}
{'name': 'adobegc_c34072', 'size': 826, 'mtime': 1591036933.363417}
{'name': 'adobegc_c48120', 'size': 827, 'mtime': 1587061454.0755453}
{'name': 'adobegc_c56920', 'size': 827, 'mtime': 1589826993.0616467}
{'name': 'adobegc_c59448', 'size': 827, 'mtime': 1586191349.8506114}
{'name': 'adobegc_c60376', 'size': 827, 'mtime': 1584984292.1612866}
{'name': 'adobegc_c72972', 'size': 827, 'mtime': 1588626413.0896137}
{'name': 'adobegc_c76636', 'size': 827, 'mtime': 1581010218.0554078}
{'name': 'adobegc_c82640', 'size': 827, 'mtime': 1588620613.321756}
{'name': 'adobegc_c84680', 'size': 827, 'mtime': 1588623117.9436429}
{'name': 'adobegc_c87796', 'size': 827, 'mtime': 1588620230.1520216}
{'name': 'adobegc_c92020', 'size': 827, 'mtime': 1588626141.4125187}
{'name': 'adobegc_c92332', 'size': 827, 'mtime': 1588617496.3456864}
{'name': 'adobegc_c93968', 'size': 827, 'mtime': 1588619998.5936964}
{'name': 'adobegc_c94428', 'size': 827, 'mtime': 1588625116.0481493}
{'name': 'adobegc_c94564', 'size': 827, 'mtime': 1587400814.941493}
{'name': 'adobegc_c95268', 'size': 827, 'mtime': 1588618430.4614644}
{'name': 'adobegc_c95992', 'size': 827, 'mtime': 1588625744.1483426}
{'name': 'adobegc_c97532', 'size': 827, 'mtime': 1588623768.123971}
{'name': 'adobegc_c97576', 'size': 827, 'mtime': 1588699912.811693}
{'name': 'adobegc_c98628', 'size': 827, 'mtime': 1588707823.850915}
{'name': 'adobegc_c99416', 'size': 827, 'mtime': 1588705942.7441413}
{'name': 'adobegc_c99944', 'size': 827, 'mtime': 1588700140.0327764}
{'name': 'adobegc_d00736', 'size': 827, 'mtime': 1588706212.1906126}
{'name': 'adobegc_d02844', 'size': 827, 'mtime': 1588704712.9487145}
{'name': 'adobegc_d02940', 'size': 827, 'mtime': 1588705320.1099153}
{'name': 'adobegc_d03984', 'size': 827, 'mtime': 1588708248.2397952}
{'name': 'adobegc_d04400', 'size': 827, 'mtime': 1588698820.0670853}
{'name': 'adobegc_d12180', 'size': 827, 'mtime': 1580146895.6547296}
{'name': 'adobegc_d15992', 'size': 826, 'mtime': 1594664447.5050478}
{'name': 'adobegc_d20696', 'size': 827, 'mtime': 1588883151.742091}
{'name': 'adobegc_d34072', 'size': 826, 'mtime': 1591036946.3382795}
{'name': 'adobegc_d56920', 'size': 827, 'mtime': 1589827011.6453788}
{'name': 'adobegc_d59448', 'size': 827, 'mtime': 1586191396.4112055}
{'name': 'adobegc_d60376', 'size': 827, 'mtime': 1584984310.4665244}
{'name': 'adobegc_d72972', 'size': 827, 'mtime': 1588626429.153277}
{'name': 'adobegc_d76636', 'size': 827, 'mtime': 1581010315.7584887}
{'name': 'adobegc_d82640', 'size': 827, 'mtime': 1588620653.094543}
{'name': 'adobegc_d84680', 'size': 827, 'mtime': 1588623140.4772713}
{'name': 'adobegc_d87796', 'size': 827, 'mtime': 1588620294.8475337}
{'name': 'adobegc_d92020', 'size': 827, 'mtime': 1588626228.1945815}
{'name': 'adobegc_d94428', 'size': 827, 'mtime': 1588625122.2906866}
{'name': 'adobegc_d94564', 'size': 827, 'mtime': 1587400828.0741277}
{'name': 'adobegc_d95268', 'size': 827, 'mtime': 1588618440.307652}
{'name': 'adobegc_d97532', 'size': 827, 'mtime': 1588623787.4921527}
{'name': 'adobegc_d97576', 'size': 827, 'mtime': 1588699931.81901}
{'name': 'adobegc_d98628', 'size': 827, 'mtime': 1588707855.1049612}
{'name': 'adobegc_e00736', 'size': 827, 'mtime': 1588706245.611989}
{'name': 'adobegc_e02844', 'size': 827, 'mtime': 1588704734.7796671}
{'name': 'adobegc_e02940', 'size': 827, 'mtime': 1588705346.8015952}
{'name': 'adobegc_e03984', 'size': 827, 'mtime': 1588708267.3839262}
{'name': 'adobegc_e04400', 'size': 827, 'mtime': 1588698844.0438626}
{'name': 'adobegc_e12180', 'size': 827, 'mtime': 1580146918.2748847}
{'name': 'adobegc_e15992', 'size': 826, 'mtime': 1594664462.674065}
{'name': 'adobegc_e34072', 'size': 826, 'mtime': 1591036960.5743244}
{'name': 'adobegc_e56920', 'size': 827, 'mtime': 1589827029.9772768}
{'name': 'adobegc_e59448', 'size': 827, 'mtime': 1586191423.5797856}
{'name': 'adobegc_e60376', 'size': 827, 'mtime': 1584984320.550245}
{'name': 'adobegc_e72972', 'size': 827, 'mtime': 1588626449.11985}
{'name': 'adobegc_e82640', 'size': 827, 'mtime': 1588620658.7476456}
{'name': 'adobegc_e84680', 'size': 827, 'mtime': 1588623162.9596686}
{'name': 'adobegc_e87796', 'size': 827, 'mtime': 1588620363.3213055}
{'name': 'adobegc_e92020', 'size': 827, 'mtime': 1588626236.2562673}
{'name': 'adobegc_e94428', 'size': 827, 'mtime': 1588625177.8788607}
{'name': 'adobegc_e94564', 'size': 827, 'mtime': 1587400848.3485818}
{'name': 'adobegc_e97532', 'size': 827, 'mtime': 1588623800.5197835}
{'name': 'adobegc_e97576', 'size': 827, 'mtime': 1588699954.884931}
{'name': 'adobegc_e98628', 'size': 827, 'mtime': 1588707930.3610473}
{'name': 'adobegc_f00736', 'size': 827, 'mtime': 1588706262.6876884}
{'name': 'adobegc_f02844', 'size': 827, 'mtime': 1588704857.8128686}
{'name': 'adobegc_f02940', 'size': 827, 'mtime': 1588705386.8754816}
{'name': 'adobegc_f03984', 'size': 827, 'mtime': 1588708377.0388029}
{'name': 'adobegc_f04400', 'size': 827, 'mtime': 1588698865.876907}
{'name': 'adobegc_f12180', 'size': 827, 'mtime': 1580146941.4048574}
{'name': 'adobegc_f15992', 'size': 826, 'mtime': 1594664480.5364697}
{'name': 'adobegc_f59448', 'size': 827, 'mtime': 1586191468.308414}
{'name': 'adobegc_f60376', 'size': 827, 'mtime': 1584984342.4760692}
{'name': 'adobegc_f72972', 'size': 827, 'mtime': 1588626520.413051}
{'name': 'adobegc_f82640', 'size': 827, 'mtime': 1588620707.6957185}
{'name': 'adobegc_f84680', 'size': 827, 'mtime': 1588623185.9664042}
{'name': 'adobegc_f87796', 'size': 827, 'mtime': 1588620372.2095447}
{'name': 'adobegc_f94428', 'size': 827, 'mtime': 1588625198.4473124}
{'name': 'adobegc_f98628', 'size': 827, 'mtime': 1588707956.3923628}
{'name': 'adobegc_g00736', 'size': 827, 'mtime': 1588706340.7434888}
{'name': 'adobegc_g02844', 'size': 827, 'mtime': 1588704879.0104535}
{'name': 'adobegc_g02940', 'size': 827, 'mtime': 1588705417.8788993}
{'name': 'adobegc_g03984', 'size': 827, 'mtime': 1588708394.9106903}
{'name': 'adobegc_g04400', 'size': 827, 'mtime': 1588698895.7362301}
{'name': 'adobegc_g12180', 'size': 827, 'mtime': 1580146949.484896}
{'name': 'adobegc_g72972', 'size': 827, 'mtime': 1588626624.4677527}
{'name': 'adobegc_g82640', 'size': 827, 'mtime': 1588620723.5959775}
{'name': 'adobegc_g84680', 'size': 827, 'mtime': 1588623225.1320856}
{'name': 'adobegc_g87796', 'size': 827, 'mtime': 1588620425.5512018}
{'name': 'adobegc_g94428', 'size': 827, 'mtime': 1588625228.557094}
{'name': 'adobegc_h00736', 'size': 827, 'mtime': 1588706456.0406094}
{'name': 'adobegc_h02844', 'size': 827, 'mtime': 1588704948.776196}
{'name': 'adobegc_h02940', 'size': 827, 'mtime': 1588705450.0687082}
{'name': 'adobegc_h03984', 'size': 827, 'mtime': 1588708415.418625}
{'name': 'adobegc_h04400', 'size': 827, 'mtime': 1588698929.891593}
{'name': 'adobegc_h12180', 'size': 827, 'mtime': 1580146955.5651238}
{'name': 'adobegc_h82640', 'size': 827, 'mtime': 1588620743.5954738}
{'name': 'adobegc_h84680', 'size': 827, 'mtime': 1588623352.3280022}
{'name': 'adobegc_h87796', 'size': 827, 'mtime': 1588620447.1586652}
{'name': 'adobegc_h94428', 'size': 827, 'mtime': 1588625239.4658115}
{'name': 'adobegc_i00736', 'size': 827, 'mtime': 1588706484.0562284}
{'name': 'adobegc_i02940', 'size': 827, 'mtime': 1588705465.7495365}
{'name': 'adobegc_i03984', 'size': 827, 'mtime': 1588708539.8739815}
{'name': 'adobegc_i04400', 'size': 827, 'mtime': 1588698952.9581492}
{'name': 'adobegc_i12180', 'size': 827, 'mtime': 1580147014.8754144}
{'name': 'adobegc_i82640', 'size': 827, 'mtime': 1588620751.6867297}
{'name': 'adobegc_i84680', 'size': 827, 'mtime': 1588623400.7245765}
{'name': 'adobegc_i87796', 'size': 827, 'mtime': 1588620470.659986}
{'name': 'adobegc_i94428', 'size': 827, 'mtime': 1588625266.8207235}
{'name': 'adobegc_j00736', 'size': 827, 'mtime': 1588706506.187664}
{'name': 'adobegc_j03984', 'size': 827, 'mtime': 1588708569.6812017}
{'name': 'adobegc_j04400', 'size': 827, 'mtime': 1588698970.8107784}
{'name': 'adobegc_j12180', 'size': 827, 'mtime': 1580147035.305319}
{'name': 'adobegc_j82640', 'size': 827, 'mtime': 1588620768.686572}
{'name': 'adobegc_j87796', 'size': 827, 'mtime': 1588620476.2220924}
{'name': 'adobegc_j94428', 'size': 827, 'mtime': 1588625305.749532}
{'name': 'adobegc_k00736', 'size': 827, 'mtime': 1588706597.5977101}
{'name': 'adobegc_k03984', 'size': 827, 'mtime': 1588708585.727807}
{'name': 'adobegc_k04400', 'size': 827, 'mtime': 1588699002.9317427}
{'name': 'adobegc_k12180', 'size': 827, 'mtime': 1580147056.48849}
{'name': 'adobegc_k94428', 'size': 827, 'mtime': 1588625326.7249243}
{'name': 'adobegc_l00736', 'size': 827, 'mtime': 1588706650.0458724}
{'name': 'adobegc_l04400', 'size': 827, 'mtime': 1588699173.7167861}
{'name': 'adobegc_l12180', 'size': 827, 'mtime': 1580147075.7756407}
{'name': 'adobegc_m00736', 'size': 827, 'mtime': 1588706696.6210747}
{'name': 'adobegc_m04400', 'size': 827, 'mtime': 1588699299.9061432}
{'name': 'adobegc_n00736', 'size': 827, 'mtime': 1588706702.6324935}
{'name': 'adobegc_n04400', 'size': 827, 'mtime': 1588699322.7834435}
{'name': 'adobegc_o04400', 'size': 827, 'mtime': 1588699343.7964466}
{'name': 'adobegc_p04400', 'size': 827, 'mtime': 1588699361.8530748}
{'name': 'adobegc_q04400', 'size': 827, 'mtime': 1588699435.7401783}
{'name': 'adobegc_r04400', 'size': 827, 'mtime': 1588699497.8403273}
{'name': 'adobegc_s04400', 'size': 827, 'mtime': 1588699564.148772}
{'name': 'adobegc_t04400', 'size': 827, 'mtime': 1588699581.2896767}
{'name': 'adobegc_u04400', 'size': 827, 'mtime': 1588699598.6942072}
{'name': 'adobegc_v04400', 'size': 827, 'mtime': 1588699628.5083873}
{'name': 'adobegc_w04400', 'size': 827, 'mtime': 1588699651.7972827}
{'name': 'AdobeIPCBrokerCustomHook.log', 'size': 110, 'mtime': 1594148255.931315}
{'name': 'ArmUI.ini', 'size': 257928, 'mtime': 1594655604.2703094}
{'name': 'bep_ie_tmp.log', 'size': 5750, 'mtime': 1594630046.8321078}
{'name': 'BROMJ6945DW.INI', 'size': 164, 'mtime': 1594932054.8597217}
{'name': 'CCSF_DebugLog.log', 'size': 22720, 'mtime': 1594619167.7750485}
{'name': 'chrome_installer.log', 'size': 215231, 'mtime': 1593199121.0920432}
{'name': 'dd_vcredist_amd64_20200710192056.log', 'size': 9218, 'mtime': 1594434073.4356828}
{'name': 'dd_vcredist_amd64_20200710192056_000_vcRuntimeMinimum_x64.log', 'size': 340038, 'mtime': 1594434071.8020437}
{'name': 'dd_vcredist_amd64_20200710192056_001_vcRuntimeAdditional_x64.log', 'size': 195928, 'mtime': 1594434073.3878088}
{'name': 'FXSAPIDebugLogFile.txt', 'size': 0, 'mtime': 1580005774.2871478}
{'name': 'FXSTIFFDebugLogFile.txt', 'size': 0, 'mtime': 1580005774.2402809}
{'name': 'install.ps1', 'size': 22662, 'mtime': 1594434168.2012112}
{'name': 'logserver.exe', 'size': 360392, 'mtime': 1591966026.0}
{'name': 'MpCmdRun.log', 'size': 414950, 'mtime': 1595033174.3764453}
{'name': 'Ofcdebug.ini', 'size': 2208, 'mtime': 1594619167.7125623}
{'name': 'ofcpipc.dll', 'size': 439232, 'mtime': 1591380338.0}
{'name': 'PDApp.log', 'size': 450550, 'mtime': 1594148263.081737}
{'name': 'show_temp_dir.py', 'size': 505, 'mtime': 1595040826.2968051}
{'name': 'tem33F0.tmp', 'size': 68, 'mtime': 1580005493.3622465}
{'name': 'temE0A2.tmp', 'size': 206, 'mtime': 1580005825.1382103}
{'name': 'tmdbg20.dll', 'size': 264648, 'mtime': 1591966082.0}
{'name': 'tm_icrcL_A606D985_38CA_41ab_BCD9_60F771CF800D', 'size': 0, 'mtime': 1594629977.2000608}
{'name': 'TS_3AD6.tmp', 'size': 262144, 'mtime': 1594629969.3296628}
{'name': 'TS_A4CE.tmp', 'size': 327680, 'mtime': 1594629996.4481225}
{'name': 'winagent-v0.9.4.exe', 'size': 13265088, 'mtime': 1594615216.1575873}
{'name': 'wuredist.cab', 'size': 6295, 'mtime': 1594458610.4993813}
"""

View File

@@ -0,0 +1,42 @@
import json
from django.conf import settings
import random
from rest_framework.response import Response
SVC_FILE = settings.BASE_DIR.joinpath("tacticalrmm/test_data/winsvcs.json")
PROCS_FILE = settings.BASE_DIR.joinpath("tacticalrmm/test_data/procs.json")
EVT_LOG_FILE = settings.BASE_DIR.joinpath("tacticalrmm/test_data/appeventlog.json")
def demo_get_services():
with open(SVC_FILE, "r") as f:
svcs = json.load(f)
return Response(svcs)
# simulate realtime process monitor
def demo_get_procs():
with open(PROCS_FILE, "r") as f:
procs = json.load(f)
ret = []
for proc in procs:
tmp = {}
for _, _ in proc.items():
tmp["name"] = proc["name"]
tmp["pid"] = proc["pid"]
tmp["membytes"] = random.randrange(423424, 938921325)
tmp["username"] = proc["username"]
tmp["id"] = proc["id"]
tmp["cpu_percent"] = "{:.2f}".format(random.uniform(0.1, 99.4))
ret.append(tmp)
return Response(ret)
def demo_get_eventlog():
with open(EVT_LOG_FILE, "r") as f:
logs = json.load(f)
return Response(logs)

View File

@@ -21,6 +21,7 @@ EXCLUDE_PATHS = (
f"/{settings.ADMIN_URL}",
"/logout",
"/agents/installer",
"/api/schema",
)
@@ -92,3 +93,61 @@ class LogIPMiddleware:
request._client_ip = client_ip
response = self.get_response(request)
return response
class DemoMiddleware:
def __init__(self, get_response):
self.get_response = get_response
self.not_allowed = [
{"name": "AgentProcesses", "methods": ["DELETE"]},
{"name": "AgentMeshCentral", "methods": ["GET", "POST"]},
{"name": "update_agents", "methods": ["POST"]},
{"name": "send_raw_cmd", "methods": ["POST"]},
{"name": "install_agent", "methods": ["POST"]},
{"name": "get_mesh_exe", "methods": ["POST"]},
{"name": "GenerateAgent", "methods": ["GET"]},
{"name": "UploadMeshAgent", "methods": ["PUT"]},
{"name": "email_test", "methods": ["POST"]},
{"name": "server_maintenance", "methods": ["POST"]},
{"name": "CodeSign", "methods": ["PATCH", "POST"]},
{"name": "TwilioSMSTest", "methods": ["POST"]},
{"name": "GetEditActionService", "methods": ["PUT", "POST"]},
{"name": "TestScript", "methods": ["POST"]},
{"name": "GetUpdateDeleteAgent", "methods": ["DELETE"]},
{"name": "Reboot", "methods": ["POST", "PATCH"]},
{"name": "recover", "methods": ["POST"]},
{"name": "run_script", "methods": ["POST"]},
{"name": "bulk", "methods": ["POST"]},
{"name": "WMI", "methods": ["POST"]},
{"name": "PolicyAutoTask", "methods": ["POST"]},
{"name": "RunAutoTask", "methods": ["POST"]},
{"name": "run_checks", "methods": ["POST"]},
{"name": "GetSoftware", "methods": ["POST", "PUT"]},
{"name": "ScanWindowsUpdates", "methods": ["POST"]},
{"name": "InstallWindowsUpdates", "methods": ["POST"]},
{"name": "PendingActions", "methods": ["DELETE"]},
]
def __call__(self, request):
return self.get_response(request)
def drf_mock_response(self, request, resp):
from rest_framework.views import APIView
view = APIView()
view.headers = view.default_response_headers
return view.finalize_response(request, resp).render() # type: ignore
def process_view(self, request, view_func, view_args, view_kwargs):
from .utils import notify_error
err = "Not available in demo"
excludes = ("/api/v3",)
if request.path.startswith(excludes):
return self.drf_mock_response(request, notify_error(err))
for i in self.not_allowed:
if view_func.__name__ == i["name"] and request.method in i["methods"]:
return self.drf_mock_response(request, notify_error(err))

View File

@@ -15,25 +15,25 @@ EXE_DIR = os.path.join(BASE_DIR, "tacticalrmm/private/exe")
AUTH_USER_MODEL = "accounts.User"
# latest release
TRMM_VERSION = "0.9.2"
TRMM_VERSION = "0.11.0"
# bump this version everytime vue code is changed
# to alert user they need to manually refresh their browser
APP_VER = "0.0.150"
APP_VER = "0.0.156"
# https://github.com/wh1te909/rmmagent
LATEST_AGENT_VER = "1.6.2"
LATEST_AGENT_VER = "1.8.0"
MESH_VER = "0.9.45"
MESH_VER = "0.9.67"
NATS_SERVER_VER = "2.3.3"
NATS_SERVER_VER = "2.6.6"
# for the update script, bump when need to recreate venv or npm install
PIP_VER = "23"
NPM_VER = "24"
PIP_VER = "26"
NPM_VER = "28"
SETUPTOOLS_VER = "58.5.3"
WHEEL_VER = "0.37.0"
SETUPTOOLS_VER = "59.6.0"
WHEEL_VER = "0.37.1"
DL_64 = f"https://github.com/wh1te909/rmmagent/releases/download/v{LATEST_AGENT_VER}/winagent-v{LATEST_AGENT_VER}.exe"
DL_32 = f"https://github.com/wh1te909/rmmagent/releases/download/v{LATEST_AGENT_VER}/winagent-v{LATEST_AGENT_VER}-x86.exe"
@@ -65,6 +65,13 @@ REST_FRAMEWORK = {
"knox.auth.TokenAuthentication",
"tacticalrmm.auth.APIAuthentication",
),
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
}
SPECTACULAR_SETTINGS = {
"TITLE": "Tactical RMM API",
"DESCRIPTION": "Simple and Fast remote monitoring and management tool",
"VERSION": TRMM_VERSION,
}
if not "AZPIPELINE" in os.environ:
@@ -97,6 +104,7 @@ INSTALLED_APPS = [
"logs",
"scripts",
"alerts",
"drf_spectacular",
]
if not "AZPIPELINE" in os.environ:
@@ -137,6 +145,11 @@ MIDDLEWARE = [
if ADMIN_ENABLED: # type: ignore
MIDDLEWARE += ("django.contrib.messages.middleware.MessageMiddleware",)
try:
if DEMO: # type: ignore
MIDDLEWARE += ("tacticalrmm.middleware.DemoMiddleware",)
except:
pass
ROOT_URLCONF = "tacticalrmm.urls"

View File

@@ -0,0 +1,7 @@
$networkstatus = Get-NetConnectionProfile | Select NetworkCategory | Out-String
if ($networkstatus.Contains("DomainAuthenticated")) {
exit 0
} else {
exit 1
}

View File

@@ -0,0 +1,15 @@
$pools = Get-VirtualDisk | select -ExpandProperty HealthStatus
$err = $False
ForEach ($pool in $pools) {
if ($pool -ne "Healthy") {
$err = $True
}
}
if ($err) {
exit 1
} else {
exit 0
}

View File

@@ -0,0 +1,9 @@
@echo off
sc stop spooler
timeout /t 5 /nobreak > NUL
del C:\Windows\System32\spool\printers\* /Q /F /S
sc start spooler

View File

@@ -0,0 +1 @@
Restart-Service NlaSvc -Force

View File

@@ -0,0 +1,25 @@
import os
temp_dir = "C:\\Windows\\Temp"
files = []
total = 0
with os.scandir(temp_dir) as it:
for f in it:
file = {}
if not f.name.startswith(".") and f.is_file():
total += 1
stats = f.stat()
file["name"] = f.name
file["size"] = stats.st_size
file["mtime"] = stats.st_mtime
files.append(file)
print(f"Total files: {total}\n")
for file in files:
print(file)

View File

@@ -0,0 +1,132 @@
{
"log": [
{
"uid": 2006,
"time": "2021-01-13 15:08:05 -0800 PST",
"source": "Windows Error Reporting",
"eventID": 1001,
"message": "Fault bucket 1573205062969647577, type 5\nEvent Name: StoreAgentSearchUpdatePackagesFailure1\nResponse: Not available\nCab Id: 0\n\nProblem signature:\nP1: Update;ScanForUpdates\nP2: 80240024\nP3: 19041\nP4: 508\nP5: Windows.Desktop\nP6: S\nP7: \nP8: \nP9: \nP10: \n\nAttached files:\n\\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\Temp\\WER7055.tmp.WERInternalMetadata.xml\n\nThese files may be available here:\n\\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\ReportArchive\\NonCritical_Update;ScanForUp_91ddac622de2aa181226f1833763a645a6701e_00000000_a030f5e8-b201-4b3b-b9c9-2f90448b63db\n\nAnalysis symbol: \nRechecking for solution: 0\nReport Id: a030f5e8-b201-4b3b-b9c9-2f90448b63db\nReport Status: 268435456\nHashed bucket: 2ec09064b27edf5bb5d525ab6926e1d9\nCab Guid: 0",
"eventType": "INFO"
},
{
"uid": 2007,
"time": "2021-01-13 15:08:04 -0800 PST",
"source": "Windows Error Reporting",
"eventID": 1001,
"message": "Fault bucket , type 0\nEvent Name: StoreAgentSearchUpdatePackagesFailure1\nResponse: Not available\nCab Id: 0\n\nProblem signature:\nP1: Update;ScanForUpdates\nP2: 80240024\nP3: 19041\nP4: 508\nP5: Windows.Desktop\nP6: S\nP7: \nP8: \nP9: \nP10: \n\nAttached files:\n\nThese files may be available here:\n\\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\ReportQueue\\NonCritical_Update;ScanForUp_91ddac622de2aa181226f1833763a645a6701e_00000000_a030f5e8-b201-4b3b-b9c9-2f90448b63db\n\nAnalysis symbol: \nRechecking for solution: 0\nReport Id: a030f5e8-b201-4b3b-b9c9-2f90448b63db\nReport Status: 4\nHashed bucket: \nCab Guid: 0",
"eventType": "INFO"
},
{
"uid": 2008,
"time": "2021-01-13 15:08:02 -0800 PST",
"source": "Windows Error Reporting",
"eventID": 1001,
"message": "Fault bucket 2051817844803413223, type 5\nEvent Name: StoreAgentDownloadFailure1\nResponse: Not available\nCab Id: 0\n\nProblem signature:\nP1: Update;ScanForUpdates\nP2: 80246016\nP3: 19041\nP4: 508\nP5: Windows.Desktop\nP6: S\nP7: \nP8: \nP9: \nP10: \n\nAttached files:\n\\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\Temp\\WER66BF.tmp.WERInternalMetadata.xml\n\nThese files may be available here:\n\\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\ReportArchive\\NonCritical_Update;ScanForUp_2c3439b6a8021ac4ff7a4e7f23e5b6f1d9e0_00000000_4c8d0432-6af0-4a6b-b153-428f6d0310c9\n\nAnalysis symbol: \nRechecking for solution: 0\nReport Id: 4c8d0432-6af0-4a6b-b153-428f6d0310c9\nReport Status: 268435456\nHashed bucket: 04f95e1eb7585bb17c7985757750a4e7\nCab Guid: 0",
"eventType": "INFO"
},
{
"uid": 2009,
"time": "2021-01-13 15:08:02 -0800 PST",
"source": "Windows Error Reporting",
"eventID": 1001,
"message": "Fault bucket , type 0\nEvent Name: StoreAgentDownloadFailure1\nResponse: Not available\nCab Id: 0\n\nProblem signature:\nP1: Update;ScanForUpdates\nP2: 80246016\nP3: 19041\nP4: 508\nP5: Windows.Desktop\nP6: S\nP7: \nP8: \nP9: \nP10: \n\nAttached files:\n\nThese files may be available here:\n\\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\ReportQueue\\NonCritical_Update;ScanForUp_2c3439b6a8021ac4ff7a4e7f23e5b6f1d9e0_00000000_4c8d0432-6af0-4a6b-b153-428f6d0310c9\n\nAnalysis symbol: \nRechecking for solution: 0\nReport Id: 4c8d0432-6af0-4a6b-b153-428f6d0310c9\nReport Status: 4\nHashed bucket: \nCab Guid: 0",
"eventType": "INFO"
},
{
"uid": 2264,
"time": "2021-01-12 06:28:04 -0800 PST",
"source": "Windows Error Reporting",
"eventID": 1001,
"message": "Fault bucket 1693358677999346984, type 5\nEvent Name: StoreAgentDownloadFailure1\nResponse: Not available\nCab Id: 0\n\nProblem signature:\nP1: Update;ScanForUpdates\nP2: 80246016\nP3: 19041\nP4: 508\nP5: Windows.Desktop\nP6: 2\nP7: \nP8: \nP9: \nP10: \n\nAttached files:\n\\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\Temp\\WERFEF3.tmp.WERInternalMetadata.xml\n\nThese files may be available here:\n\\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\ReportArchive\\NonCritical_Update;ScanForUp_c7a6adc885884a9aed88424cfb7d9d742f573c1_00000000_32d7534b-30da-4a6f-aa5a-7f65a48d2d47\n\nAnalysis symbol: \nRechecking for solution: 0\nReport Id: 32d7534b-30da-4a6f-aa5a-7f65a48d2d47\nReport Status: 268435456\nHashed bucket: cdec799a6cf0bbef378004beef79e528\nCab Guid: 0",
"eventType": "INFO"
},
{
"uid": 2265,
"time": "2021-01-12 06:28:03 -0800 PST",
"source": "Windows Error Reporting",
"eventID": 1001,
"message": "Fault bucket , type 0\nEvent Name: StoreAgentDownloadFailure1\nResponse: Not available\nCab Id: 0\n\nProblem signature:\nP1: Update;ScanForUpdates\nP2: 80246016\nP3: 19041\nP4: 508\nP5: Windows.Desktop\nP6: 2\nP7: \nP8: \nP9: \nP10: \n\nAttached files:\n\nThese files may be available here:\n\\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\ReportQueue\\NonCritical_Update;ScanForUp_c7a6adc885884a9aed88424cfb7d9d742f573c1_00000000_32d7534b-30da-4a6f-aa5a-7f65a48d2d47\n\nAnalysis symbol: \nRechecking for solution: 0\nReport Id: 32d7534b-30da-4a6f-aa5a-7f65a48d2d47\nReport Status: 4\nHashed bucket: \nCab Guid: 0",
"eventType": "INFO"
},
{
"uid": 2636,
"time": "2021-01-10 07:13:43 -0800 PST",
"source": "Windows Error Reporting",
"eventID": 1001,
"message": "Fault bucket 1573205062969647577, type 5\nEvent Name: StoreAgentSearchUpdatePackagesFailure1\nResponse: Not available\nCab Id: 0\n\nProblem signature:\nP1: Update;ScanForUpdates\nP2: 80240024\nP3: 19041\nP4: 508\nP5: Windows.Desktop\nP6: S\nP7: \nP8: \nP9: \nP10: \n\nAttached files:\n\\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\Temp\\WER118F.tmp.WERInternalMetadata.xml\n\nThese files may be available here:\n\\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\ReportArchive\\NonCritical_Update;ScanForUp_91ddac622de2aa181226f1833763a645a6701e_00000000_d75f7cf4-1ba2-4a8f-a4fb-f0336137aeb9\n\nAnalysis symbol: \nRechecking for solution: 0\nReport Id: d75f7cf4-1ba2-4a8f-a4fb-f0336137aeb9\nReport Status: 268435456\nHashed bucket: 2ec09064b27edf5bb5d525ab6926e1d9\nCab Guid: 0",
"eventType": "INFO"
},
{
"uid": 2637,
"time": "2021-01-10 07:13:42 -0800 PST",
"source": "Windows Error Reporting",
"eventID": 1001,
"message": "Fault bucket , type 0\nEvent Name: StoreAgentSearchUpdatePackagesFailure1\nResponse: Not available\nCab Id: 0\n\nProblem signature:\nP1: Update;ScanForUpdates\nP2: 80240024\nP3: 19041\nP4: 508\nP5: Windows.Desktop\nP6: S\nP7: \nP8: \nP9: \nP10: \n\nAttached files:\n\nThese files may be available here:\n\\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\ReportQueue\\NonCritical_Update;ScanForUp_91ddac622de2aa181226f1833763a645a6701e_00000000_d75f7cf4-1ba2-4a8f-a4fb-f0336137aeb9\n\nAnalysis symbol: \nRechecking for solution: 0\nReport Id: d75f7cf4-1ba2-4a8f-a4fb-f0336137aeb9\nReport Status: 4\nHashed bucket: \nCab Guid: 0",
"eventType": "INFO"
},
{
"uid": 2638,
"time": "2021-01-10 07:13:40 -0800 PST",
"source": "Windows Error Reporting",
"eventID": 1001,
"message": "Fault bucket 2051817844803413223, type 5\nEvent Name: StoreAgentDownloadFailure1\nResponse: Not available\nCab Id: 0\n\nProblem signature:\nP1: Update;ScanForUpdates\nP2: 80246016\nP3: 19041\nP4: 508\nP5: Windows.Desktop\nP6: S\nP7: \nP8: \nP9: \nP10: \n\nAttached files:\n\\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\Temp\\WER867.tmp.WERInternalMetadata.xml\n\nThese files may be available here:\n\\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\ReportArchive\\NonCritical_Update;ScanForUp_2c3439b6a8021ac4ff7a4e7f23e5b6f1d9e0_00000000_e423f369-52f4-4cf1-98b7-6e519dad9836\n\nAnalysis symbol: \nRechecking for solution: 0\nReport Id: e423f369-52f4-4cf1-98b7-6e519dad9836\nReport Status: 268435456\nHashed bucket: 04f95e1eb7585bb17c7985757750a4e7\nCab Guid: 0",
"eventType": "INFO"
},
{
"uid": 2639,
"time": "2021-01-10 07:13:40 -0800 PST",
"source": "Windows Error Reporting",
"eventID": 1001,
"message": "Fault bucket , type 0\nEvent Name: StoreAgentDownloadFailure1\nResponse: Not available\nCab Id: 0\n\nProblem signature:\nP1: Update;ScanForUpdates\nP2: 80246016\nP3: 19041\nP4: 508\nP5: Windows.Desktop\nP6: S\nP7: \nP8: \nP9: \nP10: \n\nAttached files:\n\nThese files may be available here:\n\\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\ReportQueue\\NonCritical_Update;ScanForUp_2c3439b6a8021ac4ff7a4e7f23e5b6f1d9e0_00000000_e423f369-52f4-4cf1-98b7-6e519dad9836\n\nAnalysis symbol: \nRechecking for solution: 0\nReport Id: e423f369-52f4-4cf1-98b7-6e519dad9836\nReport Status: 4\nHashed bucket: \nCab Guid: 0",
"eventType": "INFO"
},
{
"uid": 5553,
"time": "2020-12-25 15:45:02 -0800 PST",
"source": "Windows Error Reporting",
"eventID": 1001,
"message": "Fault bucket 1988976340961221197, type 5\nEvent Name: StoreAgentSearchUpdatePackagesFailure1\nResponse: Not available\nCab Id: 0\n\nProblem signature:\nP1: Update;ScanForUpdates\nP2: 80240024\nP3: 19041\nP4: 508\nP5: Windows.Desktop\nP6: G\nP7: \nP8: \nP9: \nP10: \n\nAttached files:\n\\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\Temp\\WER784D.tmp.WERInternalMetadata.xml\n\nThese files may be available here:\n\\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\ReportArchive\\NonCritical_Update;ScanForUp_2ae3e9964efbef3189977773239bfa7f29278_00000000_a20518d3-26b9-4e16-9223-924bc99df211\n\nAnalysis symbol: \nRechecking for solution: 0\nReport Id: a20518d3-26b9-4e16-9223-924bc99df211\nReport Status: 268435456\nHashed bucket: d6c816dde23870028b9a4371ada65a4d\nCab Guid: 0",
"eventType": "INFO"
},
{
"uid": 5554,
"time": "2020-12-25 15:45:02 -0800 PST",
"source": "Windows Error Reporting",
"eventID": 1001,
"message": "Fault bucket , type 0\nEvent Name: StoreAgentSearchUpdatePackagesFailure1\nResponse: Not available\nCab Id: 0\n\nProblem signature:\nP1: Update;ScanForUpdates\nP2: 80240024\nP3: 19041\nP4: 508\nP5: Windows.Desktop\nP6: G\nP7: \nP8: \nP9: \nP10: \n\nAttached files:\n\nThese files may be available here:\n\\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\ReportQueue\\NonCritical_Update;ScanForUp_2ae3e9964efbef3189977773239bfa7f29278_00000000_a20518d3-26b9-4e16-9223-924bc99df211\n\nAnalysis symbol: \nRechecking for solution: 0\nReport Id: a20518d3-26b9-4e16-9223-924bc99df211\nReport Status: 4\nHashed bucket: \nCab Guid: 0",
"eventType": "INFO"
},
{
"uid": 5555,
"time": "2020-12-25 15:45:00 -0800 PST",
"source": "Windows Error Reporting",
"eventID": 1001,
"message": "Fault bucket 1198513334388591380, type 5\nEvent Name: StoreAgentDownloadFailure1\nResponse: Not available\nCab Id: 0\n\nProblem signature:\nP1: Update;ScanForUpdates\nP2: 80246016\nP3: 19041\nP4: 508\nP5: Windows.Desktop\nP6: G\nP7: \nP8: \nP9: \nP10: \n\nAttached files:\n\\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\Temp\\WER6F15.tmp.WERInternalMetadata.xml\n\nThese files may be available here:\n\\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\ReportArchive\\NonCritical_Update;ScanForUp_11349d50384236752c9ebf31943a81bc9c22_00000000_d7901c46-3ff4-4e71-8d09-cb4511c1f790\n\nAnalysis symbol: \nRechecking for solution: 0\nReport Id: d7901c46-3ff4-4e71-8d09-cb4511c1f790\nReport Status: 268435456\nHashed bucket: ca587589ef5ddc16c0a1f98712cd0b14\nCab Guid: 0",
"eventType": "INFO"
},
{
"uid": 5556,
"time": "2020-12-25 15:44:59 -0800 PST",
"source": "Windows Error Reporting",
"eventID": 1001,
"message": "Fault bucket , type 0\nEvent Name: StoreAgentDownloadFailure1\nResponse: Not available\nCab Id: 0\n\nProblem signature:\nP1: Update;ScanForUpdates\nP2: 80246016\nP3: 19041\nP4: 508\nP5: Windows.Desktop\nP6: G\nP7: \nP8: \nP9: \nP10: \n\nAttached files:\n\nThese files may be available here:\n\\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\ReportQueue\\NonCritical_Update;ScanForUp_11349d50384236752c9ebf31943a81bc9c22_00000000_d7901c46-3ff4-4e71-8d09-cb4511c1f790\n\nAnalysis symbol: \nRechecking for solution: 0\nReport Id: d7901c46-3ff4-4e71-8d09-cb4511c1f790\nReport Status: 4\nHashed bucket: \nCab Guid: 0",
"eventType": "INFO"
},
{
"uid": 7058,
"time": "2020-12-17 10:59:07 -0800 PST",
"source": "Windows Error Reporting",
"eventID": 1001,
"message": "Fault bucket 1324913600230033009, type 5\nEvent Name: StoreAgentDownloadFailure1\nResponse: Not available\nCab Id: 0\n\nProblem signature:\nP1: Update;ScanForUpdates\nP2: 80073cf9\nP3: 19041\nP4: 508\nP5: Windows.Desktop\nP6: 8\nP7: \nP8: \nP9: \nP10: \n\nAttached files:\n\\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\Temp\\WERD7E0.tmp.WERInternalMetadata.xml\n\nThese files may be available here:\n\\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\ReportArchive\\NonCritical_Update;ScanForUp_f5657215ea2368b3b590dfaee6ea708ec7f61_00000000_979cc6e7-b0ab-42ac-8127-1fec5a11c639\n\nAnalysis symbol: \nRechecking for solution: 0\nReport Id: 979cc6e7-b0ab-42ac-8127-1fec5a11c639\nReport Status: 268435456\nHashed bucket: 39ea3113ccdc8abbd26309e653cb8671\nCab Guid: 0",
"eventType": "INFO"
},
{
"uid": 7059,
"time": "2020-12-17 10:59:06 -0800 PST",
"source": "Windows Error Reporting",
"eventID": 1001,
"message": "Fault bucket , type 0\nEvent Name: StoreAgentDownloadFailure1\nResponse: Not available\nCab Id: 0\n\nProblem signature:\nP1: Update;ScanForUpdates\nP2: 80073cf9\nP3: 19041\nP4: 508\nP5: Windows.Desktop\nP6: 8\nP7: \nP8: \nP9: \nP10: \n\nAttached files:\n\nThese files may be available here:\n\\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\ReportQueue\\NonCritical_Update;ScanForUp_f5657215ea2368b3b590dfaee6ea708ec7f61_00000000_979cc6e7-b0ab-42ac-8127-1fec5a11c639\n\nAnalysis symbol: \nRechecking for solution: 0\nReport Id: 979cc6e7-b0ab-42ac-8127-1fec5a11c639\nReport Status: 4\nHashed bucket: \nCab Guid: 0",
"eventType": "INFO"
}
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,138 @@
[
{
"name": "7-Zip 19.00 (x64)",
"version": "19.00"
},
{
"name": "Mesh Agent background service",
"version": "Not Found"
},
{
"name": "MeshCentral Agent - Remote Control Software",
"version": "1.0.0"
},
{
"name": "Microsoft Visual Studio 2010 Tools for Office Runtime (x64)",
"version": "10.0.50903,10.0.50908"
},
{
"name": "Microsoft 365 Apps for enterprise - en-us",
"version": "16.0.13001.20266"
},
{
"name": "Microsoft Visual C++ 2013 x64 Additional Runtime - 12.0.40664",
"version": "12.0.40664"
},
{
"name": "Microsoft Visual C++ 2010 x64 Redistributable - 10.0.40219",
"version": "10.0.40219"
},
{
"name": "Microsoft Visual C++ 2017 x64 Minimum Runtime - 14.13.26020",
"version": "14.13.26020"
},
{
"name": "{4CEC2908-5CE4-48F0-A717-8FC833D8017A}",
"version": "0.1.247"
},
{
"name": "Microsoft Visual C++ 2013 x64 Minimum Runtime - 12.0.40664",
"version": "12.0.40664"
},
{
"name": "Google Chrome",
"version": "83.0.4103.116"
},
{
"name": "Office 16 Click-to-Run Licensing Component",
"version": "16.0.13001.20266"
},
{
"name": "Office 16 Click-to-Run Extensibility Component",
"version": "16.0.13001.20144"
},
{
"name": "Office 16 Click-to-Run Localization Component",
"version": "16.0.13001.20144"
},
{
"name": "Microsoft Visual C++ 2017 x64 Additional Runtime - 14.13.26020",
"version": "14.13.26020"
},
{
"name": "Trend Micro Security Agent",
"version": "6.7.1364"
},
{
"name": "Salt Minion 1.0.3 (Python 3)",
"version": "1.0.3"
},
{
"name": "Microsoft Visual C++ 2013 Redistributable (x64) - 12.0.40664",
"version": "12.0.40664.0"
},
{
"name": "Tactical RMM Agent",
"version": "0.9.4"
},
{
"name": "LogMeIn Client",
"version": "1.3.4952"
},
{
"name": "Microsoft Visual C++ 2017 Redistributable (x86) - 14.13.26020",
"version": "14.13.26020.0"
},
{
"name": "Google Update Helper",
"version": "1.3.35.451"
},
{
"name": "Teams Machine-Wide Installer",
"version": "1.3.0.4461"
},
{
"name": "LogMeIn",
"version": "4.1.13508"
},
{
"name": "Microsoft Visual C++ 2017 Redistributable (x64) - 14.13.26020",
"version": "14.13.26020.0"
},
{
"name": "Microsoft Visual C++ 2017 x86 Additional Runtime - 14.13.26020",
"version": "14.13.26020"
},
{
"name": "Microsoft Visual C++ 2017 x86 Minimum Runtime - 14.13.26020",
"version": "14.13.26020"
},
{
"name": "Printer Installer Client",
"version": "25.0.0.266"
},
{
"name": "Adobe Acrobat X Pro - English, Fran\u00e7ais, Deutsch",
"version": "10.1.1"
},
{
"name": "Microsoft Visual C++ 2010 x86 Redistributable - 10.0.40219",
"version": "10.0.40219"
},
{
"name": "Intel(R) Processor Graphics",
"version": "20.19.15.4835"
},
{
"name": "Realtek High Definition Audio Driver",
"version": "6.0.1.7548"
},
{
"name": "Microsoft OneDrive",
"version": "20.114.0607.0002"
},
{
"name": "Microsoft Teams",
"version": "1.3.00.13565"
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,319 @@
{
"samplecomputer": {
"07609d43-d518-4e77-856e-d1b316d1b8a8": {
"guid": "07609d43-d518-4e77-856e-d1b316d1b8a8",
"Title": "MSXML 6.0 RTM Security Update (925673)",
"Type": "Software",
"Description": "A vulnerability exists in Microsoft XML Core Services that could allow for information disclosure because the XMLHTTP ActiveX control incorrectly interprets an HTTP server-side redirect.",
"Downloaded": true,
"Installed": true,
"Mandatory": false,
"EULAAccepted": true,
"NeedsReboot": false,
"Severity": "Critical",
"UserInput": false,
"RebootBehavior": "Can Require Reboot",
"KBs": [
"KB925673"
],
"Categories": [
"Security Updates",
"SQL Server Feature Pack"
]
},
"729a0dcb-df9e-4d02-b603-ed1aee074428": {
"guid": "729a0dcb-df9e-4d02-b603-ed1aee074428",
"Title": "Security Update for Microsoft Visual C++ 2008 Service Pack 1 Redistributable Package (KB2538243)",
"Type": "Software",
"Description": "A security issue has been identified leading to MFC application vulnerability in DLL planting due to MFC not specifying the full path to system/localization DLLs. You can protect your computer by installing this update from Microsoft. After you install this item, you may have to restart your computer.",
"Downloaded": true,
"Installed": true,
"Mandatory": false,
"EULAAccepted": true,
"NeedsReboot": false,
"Severity": "Important",
"UserInput": false,
"RebootBehavior": "Can Require Reboot",
"KBs": [
"KB2538243"
],
"Categories": [
"Security Updates",
"Visual Studio 2008"
]
},
"527b2c0c-b10b-433d-9e35-4be03c28768a": {
"guid": "527b2c0c-b10b-433d-9e35-4be03c28768a",
"Title": "Update for Microsoft Office 2010 (KB2553347) 64-Bit Edition",
"Type": "Software",
"Description": "Microsoft has released an update for Microsoft Office 2010 64-Bit Edition. This update provides the latest fixes to Microsoft Office 2010 64-Bit Edition. Additionally, this update contains stability and performance improvements.",
"Downloaded": true,
"Installed": true,
"Mandatory": false,
"EULAAccepted": true,
"NeedsReboot": false,
"Severity": "",
"UserInput": false,
"RebootBehavior": "Can Require Reboot",
"KBs": [
"KB2553347"
],
"Categories": [
"Critical Updates",
"Office 2010"
]
},
"7a7f49fc-15e8-4760-b750-e7c57d1bdb02": {
"guid": "7a7f49fc-15e8-4760-b750-e7c57d1bdb02",
"Title": "Security Update for Microsoft Office 2010 (KB4022206) 64-Bit Edition",
"Type": "Software",
"Description": "A security vulnerability exists in Microsoft Office 2010 64-Bit Edition that could allow arbitrary code to run when a maliciously modified file is opened. This update resolves that vulnerability.",
"Downloaded": true,
"Installed": true,
"Mandatory": false,
"EULAAccepted": true,
"NeedsReboot": false,
"Severity": "",
"UserInput": false,
"RebootBehavior": "Can Require Reboot",
"KBs": [
"KB4022206"
],
"Categories": [
"Office 2010",
"Security Updates"
]
},
"c168fa28-799f-4643-a45b-23d9d50875a9": {
"guid": "c168fa28-799f-4643-a45b-23d9d50875a9",
"Title": "Update for Microsoft Office 2010 (KB4461579) 64-Bit Edition",
"Type": "Software",
"Description": "Microsoft has released an update for Microsoft Office 2010 64-Bit Edition. This update provides the latest fixes to Microsoft Office 2010 64-Bit Edition. Additionally, this update contains stability and performance improvements.",
"Downloaded": true,
"Installed": true,
"Mandatory": false,
"EULAAccepted": true,
"NeedsReboot": false,
"Severity": "",
"UserInput": false,
"RebootBehavior": "Can Require Reboot",
"KBs": [
"KB4461579"
],
"Categories": [
"Critical Updates",
"Office 2010"
]
},
"ca3bb521-a8ea-4e26-a563-2ad6e3108b9a": {
"guid": "ca3bb521-a8ea-4e26-a563-2ad6e3108b9a",
"Title": "Microsoft Silverlight (KB4481252)",
"Type": "Software",
"Description": "Microsoft Silverlight is a Web browser plug-in for Windows and Mac OS X that delivers high quality video/audio, animation, and richer Website experiences in popular Web browsers.",
"Downloaded": false,
"Installed": false,
"Mandatory": false,
"EULAAccepted": false,
"NeedsReboot": false,
"Severity": "",
"UserInput": false,
"RebootBehavior": "Never Requires Reboot",
"KBs": [
"KB4481252"
],
"Categories": [
"Feature Packs",
"Silverlight"
]
},
"1edff8d4-bc5c-44e3-93ee-d123b6fd5c05": {
"guid": "1edff8d4-bc5c-44e3-93ee-d123b6fd5c05",
"Title": "Update for Microsoft Office 2010 (KB2589339) 64-Bit Edition",
"Type": "Software",
"Description": "Microsoft has released an update for Microsoft Office 2010 64-Bit Edition. This update provides the latest fixes to Microsoft Office 2010 64-Bit Edition. Additionally, this update contains stability and performance improvements.",
"Downloaded": true,
"Installed": true,
"Mandatory": false,
"EULAAccepted": true,
"NeedsReboot": false,
"Severity": "",
"UserInput": false,
"RebootBehavior": "Can Require Reboot",
"KBs": [
"KB2589339"
],
"Categories": [
"Critical Updates",
"Office 2010"
]
},
"c9805b1b-e4e4-4179-91c5-fb3a57aa3368": {
"guid": "c9805b1b-e4e4-4179-91c5-fb3a57aa3368",
"Title": "Security Update for Microsoft Office 2010 (KB4484238) 64-Bit Edition",
"Type": "Software",
"Description": "A security vulnerability exists in Microsoft Office 2010 64-Bit Edition that could allow arbitrary code to run when a maliciously modified file is opened. This update resolves that vulnerability.",
"Downloaded": false,
"Installed": false,
"Mandatory": false,
"EULAAccepted": true,
"NeedsReboot": false,
"Severity": "Important",
"UserInput": false,
"RebootBehavior": "Can Require Reboot",
"KBs": [
"KB4484238"
],
"Categories": [
"Office 2010",
"Security Updates"
]
},
"2221dd34-39bb-4f16-b320-be49fe4a6b95": {
"guid": "2221dd34-39bb-4f16-b320-be49fe4a6b95",
"Title": "Windows Malicious Software Removal Tool x64 - v5.82 (KB890830)",
"Type": "Software",
"Description": "After the download, this tool runs one time to check your computer for infection by specific, prevalent malicious software (including Blaster, Sasser, and Mydoom) and helps remove any infection that is found. If an infection is found, the tool will display a status report the next time that you start your computer. A new version of the tool will be offered every month. If you want to manually run the tool on your computer, you can download a copy from the Microsoft Download Center, or you can run an online version from microsoft.com. This tool is not a replacement for an antivirus product. To help protect your computer, you should use an antivirus product.",
"Downloaded": true,
"Installed": false,
"Mandatory": false,
"EULAAccepted": true,
"NeedsReboot": false,
"Severity": "",
"UserInput": false,
"RebootBehavior": "Can Require Reboot",
"KBs": [
"KB890830"
],
"Categories": [
"Update Rollups",
"Windows Server 2016",
"Windows Server 2019"
]
},
"884a6101-3b1a-4b53-bede-2f8b6bf14772": {
"guid": "884a6101-3b1a-4b53-bede-2f8b6bf14772",
"Title": "2020-01 Update for Windows Server 2019 for x64-based Systems (KB4494174)",
"Type": "Software",
"Description": "Install this update to resolve issues in Windows. For a complete listing of the issues that are included in this update, see the associated Microsoft Knowledge Base article for more information. After you install this item, you may have to restart your computer.",
"Downloaded": true,
"Installed": true,
"Mandatory": false,
"EULAAccepted": true,
"NeedsReboot": false,
"Severity": "",
"UserInput": false,
"RebootBehavior": "Can Require Reboot",
"KBs": [
"KB4494174"
],
"Categories": [
"Updates",
"Windows Server 2019"
]
},
"df5a6ec0-890e-4fb4-9a91-df999a4a5c46": {
"guid": "df5a6ec0-890e-4fb4-9a91-df999a4a5c46",
"Title": "Security Update for Microsoft Office 2010 (KB4484373) 64-Bit Edition",
"Type": "Software",
"Description": "A security vulnerability exists in Microsoft Office 2010 64-Bit Edition that could allow arbitrary code to run when a maliciously modified file is opened. This update resolves that vulnerability.",
"Downloaded": true,
"Installed": false,
"Mandatory": false,
"EULAAccepted": true,
"NeedsReboot": false,
"Severity": "Important",
"UserInput": false,
"RebootBehavior": "Can Require Reboot",
"KBs": [
"KB4484373"
],
"Categories": [
"Office 2010",
"Security Updates"
]
},
"14b1604e-c818-4fae-b1df-2bd789ec173a": {
"guid": "14b1604e-c818-4fae-b1df-2bd789ec173a",
"Title": "2020-06 Security Update for Adobe Flash Player for Windows Server 2019 for x64-based Systems (KB4561600)",
"Type": "Software",
"Description": "A security issue has been identified in a Microsoft software product that could affect your system. You can help protect your system by installing this update from Microsoft. For a complete listing of the issues that are included in this update, see the associated Microsoft Knowledge Base article. After you install this update, you may have to restart your system.",
"Downloaded": true,
"Installed": false,
"Mandatory": false,
"EULAAccepted": true,
"NeedsReboot": false,
"Severity": "Critical",
"UserInput": false,
"RebootBehavior": "Can Require Reboot",
"KBs": [
"KB4561600"
],
"Categories": [
"Security Updates",
"Windows Server 2019"
]
},
"9aab9121-0766-4f06-8204-61da23cc34b9": {
"guid": "9aab9121-0766-4f06-8204-61da23cc34b9",
"Title": "SQL Server 2019 RTM Cumulative Update (CU) 5 KB4552255",
"Type": "Software",
"Description": "CU5 for SQL Server 2019 RTM upgraded all SQL Server 2019 RTM instances and components installed through the SQL Server setup. CU5 can upgrade all editions and servicing levels of SQL Server 2019 RTM to the CU5 level. For customers in need of additional installation options, please visit the Microsoft Download Center to download the latest Cumulative Update (https://support.microsoft.com/en-us/kb/957826). To learn more about SQL Server 2019 RTM CU5, please visit the Microsoft Support (http://support.microsoft.com) Knowledge Base article KB4552255.",
"Downloaded": false,
"Installed": false,
"Mandatory": false,
"EULAAccepted": true,
"NeedsReboot": false,
"Severity": "",
"UserInput": false,
"RebootBehavior": "Can Require Reboot",
"KBs": [
"KB4552255"
],
"Categories": [
"Microsoft SQL Server 2019",
"Updates"
]
},
"0d506775-e391-41bd-b932-d79df9147c9b": {
"guid": "0d506775-e391-41bd-b932-d79df9147c9b",
"Title": "2020-07 Cumulative Update for .NET Framework 3.5, 4.7.2 and 4.8 for Windows Server 2019 for x64 (KB4566516)",
"Type": "Software",
"Description": "A security issue has been identified in a Microsoft software product that could affect your system. You can help protect your system by installing this update from Microsoft. For a complete listing of the issues that are included in this update, see the associated Microsoft Knowledge Base article. After you install this update, you may have to restart your system.",
"Downloaded": true,
"Installed": false,
"Mandatory": false,
"EULAAccepted": true,
"NeedsReboot": false,
"Severity": "Critical",
"UserInput": false,
"RebootBehavior": "Can Require Reboot",
"KBs": [
"KB4566516"
],
"Categories": [
"Security Updates",
"Windows Server 2019"
]
},
"0641752f-29fb-48d7-a3cf-f93dde26b82b": {
"guid": "0641752f-29fb-48d7-a3cf-f93dde26b82b",
"Title": "2020-07 Cumulative Update for Windows Server 2019 (1809) for x64-based Systems (KB4558998)",
"Type": "Software",
"Description": "Install this update to resolve issues in Windows. For a complete listing of the issues that are included in this update, see the associated Microsoft Knowledge Base article for more information. After you install this item, you may have to restart your computer.",
"Downloaded": true,
"Installed": false,
"Mandatory": false,
"EULAAccepted": true,
"NeedsReboot": false,
"Severity": "",
"UserInput": false,
"RebootBehavior": "Can Require Reboot",
"KBs": [
"KB4558998"
],
"Categories": [
"Security Updates"
]
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +1,15 @@
import json
import os
from unittest.mock import mock_open, patch
import requests
from django.conf import settings
from django.test import override_settings
from tacticalrmm.test import TacticalTestCase
from .utils import (
bitdays_to_string,
filter_software,
generate_winagent_exe,
get_bit_days,
reload_nats,
run_nats_api_cmd,
AGENT_DEFER,
)
@@ -78,12 +74,6 @@ class TestUtils(TacticalTestCase):
mock_subprocess.assert_called_once()
@patch("subprocess.run")
def test_run_nats_api_cmd(self, mock_subprocess):
ids = ["a", "b", "c"]
_ = run_nats_api_cmd("wmi", ids)
mock_subprocess.assert_called_once()
def test_bitdays_to_string(self):
a = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
all_days = [
@@ -104,11 +94,10 @@ class TestUtils(TacticalTestCase):
r = bitdays_to_string(bit_weekdays)
self.assertEqual(r, "Every day")
def test_filter_software(self):
with open(
os.path.join(settings.BASE_DIR, "tacticalrmm/test_data/software1.json")
) as f:
sw = json.load(f)
def test_defer_fields_exist(self):
from agents.models import Agent
r = filter_software(sw)
self.assertIsInstance(r, list)
fields = [i.name for i in Agent._meta.get_fields()]
for i in AGENT_DEFER:
self.assertIn(i, fields)

View File

@@ -39,11 +39,23 @@ urlpatterns = [
path("accounts/", include("accounts.urls")),
]
if hasattr(settings, "ADMIN_ENABLED") and settings.ADMIN_ENABLED:
if getattr(settings, "ADMIN_ENABLED", False):
from django.contrib import admin
urlpatterns += (path(settings.ADMIN_URL, admin.site.urls),)
if getattr(settings, "SWAGGER_ENABLED", False):
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
urlpatterns += (
path("api/schema/", SpectacularAPIView.as_view(), name="schema"),
path(
"api/schema/swagger-ui/",
SpectacularSwaggerView.as_view(url_name="schema"),
name="swagger-ui",
),
)
ws_urlpatterns = [
path("ws/dashinfo/", DashInfo.as_asgi()), # type: ignore
]

View File

@@ -1,10 +1,9 @@
import json
import os
import string
import subprocess
import tempfile
import time
from typing import Optional, Union
from typing import List, Optional, Union
import pytz
import requests
@@ -23,7 +22,7 @@ from agents.models import Agent
notify_error = lambda msg: Response(msg, status=status.HTTP_400_BAD_REQUEST)
SoftwareList = list[dict[str, str]]
AGENT_DEFER = ["wmi_detail", "services"]
WEEK_DAYS = {
"Sunday": 0x1,
@@ -35,6 +34,32 @@ WEEK_DAYS = {
"Saturday": 0x40,
}
MONTHS = {
"January": 0x1,
"February": 0x2,
"March": 0x4,
"April": 0x8,
"May": 0x10,
"June": 0x20,
"July": 0x40,
"August": 0x80,
"September": 0x100,
"October": 0x200,
"November": 0x400,
"December": 0x800,
}
WEEKS = {
"First Week": 0x1,
"Second Week": 0x2,
"Third Week": 0x4,
"Fourth Week": 0x8,
"Last Week": 0x10,
}
MONTH_DAYS = {f"{b}": 0x1 << a for a, b in enumerate(range(1, 32))}
MONTH_DAYS["Last Day"] = 0x80000000
def generate_winagent_exe(
client: int,
@@ -125,46 +150,58 @@ def get_bit_days(days: list[str]) -> int:
def bitdays_to_string(day: int) -> str:
ret = []
ret: List[str] = []
if day == 127:
return "Every day"
if day & WEEK_DAYS["Sunday"]:
ret.append("Sunday")
if day & WEEK_DAYS["Monday"]:
ret.append("Monday")
if day & WEEK_DAYS["Tuesday"]:
ret.append("Tuesday")
if day & WEEK_DAYS["Wednesday"]:
ret.append("Wednesday")
if day & WEEK_DAYS["Thursday"]:
ret.append("Thursday")
if day & WEEK_DAYS["Friday"]:
ret.append("Friday")
if day & WEEK_DAYS["Saturday"]:
ret.append("Saturday")
for key, value in WEEK_DAYS.items():
if day & int(value):
ret.append(key)
return ", ".join(ret)
def filter_software(sw: SoftwareList) -> SoftwareList:
ret: SoftwareList = []
printable = set(string.printable)
for s in sw:
ret.append(
{
"name": "".join(filter(lambda x: x in printable, s["name"])),
"version": "".join(filter(lambda x: x in printable, s["version"])),
"publisher": "".join(filter(lambda x: x in printable, s["publisher"])),
"install_date": s["install_date"],
"size": s["size"],
"source": s["source"],
"location": s["location"],
"uninstall": s["uninstall"],
}
)
def bitmonths_to_string(month: int) -> str:
ret: List[str] = []
if month == 4095:
return "Every month"
return ret
for key, value in MONTHS.items():
if month & int(value):
ret.append(key)
return ", ".join(ret)
def bitweeks_to_string(week: int) -> str:
ret: List[str] = []
if week == 31:
return "Every week"
for key, value in WEEKS.items():
if week & int(value):
ret.append(key)
return ", ".join(ret)
def bitmonthdays_to_string(day: int) -> str:
ret: List[str] = []
if day == MONTH_DAYS["Last Day"]:
return "Last day"
elif day == 2147483647 or day == 4294967295:
return "Every day"
for key, value in MONTH_DAYS.items():
if day & int(value):
ret.append(key)
return ", ".join(ret)
def convert_to_iso_duration(string: str) -> str:
tmp = string.upper()
if "D" in tmp:
return f"P{tmp.replace('D', 'DT')}"
else:
return f"PT{tmp}"
def reload_nats():
@@ -188,9 +225,8 @@ def reload_nats():
cert_file = f"/etc/letsencrypt/live/{domain}/fullchain.pem"
key_file = f"/etc/letsencrypt/live/{domain}/privkey.pem"
if hasattr(settings, "CERT_FILE") and hasattr(settings, "KEY_FILE"):
if os.path.exists(settings.CERT_FILE) and os.path.exists(settings.KEY_FILE):
cert_file = settings.CERT_FILE
key_file = settings.KEY_FILE
cert_file = settings.CERT_FILE
key_file = settings.KEY_FILE
config = {
"tls": {
@@ -239,38 +275,6 @@ KnoxAuthMiddlewareStack = lambda inner: KnoxAuthMiddlewareInstance(
)
def run_nats_api_cmd(mode: str, ids: list[str] = [], timeout: int = 30) -> None:
if mode == "wmi":
config = {
"key": settings.SECRET_KEY,
"natsurl": f"tls://{settings.ALLOWED_HOSTS[0]}:4222",
"agents": ids,
}
else:
db = settings.DATABASES["default"]
config = {
"key": settings.SECRET_KEY,
"natsurl": f"tls://{settings.ALLOWED_HOSTS[0]}:4222",
"user": db["USER"],
"pass": db["PASSWORD"],
"host": db["HOST"],
"port": int(db["PORT"]),
"dbname": db["NAME"],
}
with tempfile.NamedTemporaryFile(
dir="/opt/tactical/tmp" if settings.DOCKER_BUILD else None
) as fp:
with open(fp.name, "w") as f:
json.dump(config, f)
cmd = ["/usr/local/bin/nats-api", "-c", fp.name, "-m", mode]
try:
subprocess.run(cmd, timeout=timeout)
except Exception as e:
DebugLog.error(message=e)
def get_latest_trmm_ver() -> str:
url = "https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/api/tacticalrmm/tacticalrmm/settings.py"
try:
@@ -283,7 +287,7 @@ def get_latest_trmm_ver() -> str:
if "TRMM_VERSION" in line:
return line.split(" ")[2].strip('"')
except Exception as e:
DebugLog.error(message=e)
DebugLog.error(message=str(e))
return "error"
@@ -352,7 +356,8 @@ def replace_db_values(
if not obj:
return ""
if hasattr(obj, temp[1]):
# check if attr exists and isn't a function
if hasattr(obj, temp[1]) and not callable(getattr(obj, temp[1])):
value = f"'{getattr(obj, temp[1])}'" if quotes else getattr(obj, temp[1])
elif CustomField.objects.filter(model=model, name=temp[1]).exists():

View File

@@ -1,6 +1,6 @@
#!/bin/bash
SCRIPT_VERSION="15"
SCRIPT_VERSION="17"
SCRIPT_URL='https://raw.githubusercontent.com/wh1te909/tacticalrmm/master/backup.sh'
GREEN='\033[0;32m'
@@ -75,9 +75,9 @@ sudo tar -czvf ${tmp_dir}/confd/etc-confd.tar.gz -C /etc/conf.d .
sudo gzip -9 -c /var/lib/redis/appendonly.aof > ${tmp_dir}/redis/appendonly.aof.gz
sudo cp ${sysd}/rmm.service ${sysd}/celery.service ${sysd}/celerybeat.service ${sysd}/meshcentral.service ${sysd}/nats.service ${tmp_dir}/systemd/
if [ -f "${sysd}/daphne.service" ]; then
sudo cp ${sysd}/daphne.service ${tmp_dir}/systemd/
sudo cp ${sysd}/rmm.service ${sysd}/celery.service ${sysd}/celerybeat.service ${sysd}/meshcentral.service ${sysd}/nats.service ${sysd}/daphne.service ${tmp_dir}/systemd/
if [ -f "${sysd}/nats-api.service" ]; then
sudo cp ${sysd}/nats-api.service ${tmp_dir}/systemd/
fi
cat /rmm/api/tacticalrmm/tacticalrmm/private/log/django_debug.log | gzip -9 > ${tmp_dir}/rmm/debug.log.gz
@@ -89,4 +89,4 @@ tar -cf /rmmbackups/rmm-backup-${dt_now}.tar -C ${tmp_dir} .
rm -rf ${tmp_dir}
echo -ne "${GREEN}Backup saved to /rmmbackups/rmm-backup-${dt_now}.tar${NC}\n"
echo -ne "${GREEN}Backup saved to /rmmbackups/rmm-backup-${dt_now}.tar${NC}\n"

View File

@@ -10,6 +10,13 @@ set -e
: "${MONGODB_PORT:=27017}"
: "${NGINX_HOST_IP:=172.20.0.20}"
: "${MESH_PERSISTENT_CONFIG:=0}"
: "${WS_MASK_OVERRIDE:=0}"
: "${SMTP_HOST:=smtp.example.com}"
: "${SMTP_PORT:=587}"
: "${SMTP_FROM:=mesh@example.com}"
: "${SMTP_USER:=mesh@example.com}"
: "${SMTP_PASS:=mesh-smtp-pass}"
: "${SMTP_TLS:=false}"
mkdir -p /home/node/app/meshcentral-data
mkdir -p ${TACTICAL_DIR}/tmp
@@ -50,8 +57,17 @@ mesh_config="$(cat << EOF
"NewAccounts": false,
"mstsc": true,
"GeoLocation": true,
"CertUrl": "https://${NGINX_HOST_IP}:443"
"CertUrl": "https://${NGINX_HOST_IP}:443",
"agentConfig": [ "webSocketMaskOverride=${WS_MASK_OVERRIDE}" ]
}
},
"smtp": {
"host": "${SMTP_HOST}",
"port": ${SMTP_PORT},
"from": "${SMTP_FROM}",
"user": "${SMTP_USER}",
"pass": "${SMTP_PASS}",
"tls": ${SMTP_TLS}
}
}
EOF

View File

@@ -1,12 +1,15 @@
FROM nats:2.3.3-alpine
FROM nats:2.6.6-alpine
ENV TACTICAL_DIR /opt/tactical
ENV TACTICAL_READY_FILE ${TACTICAL_DIR}/tmp/tactical.ready
RUN apk add --no-cache inotify-tools supervisor bash
RUN apk add --no-cache supervisor bash
SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"]
COPY natsapi/bin/nats-api /usr/local/bin/
RUN chmod +x /usr/local/bin/nats-api
COPY docker/containers/tactical-nats/entrypoint.sh /
RUN chmod +x /entrypoint.sh

View File

@@ -3,11 +3,14 @@
set -e
: "${DEV:=0}"
: "${NATS_CONFIG_CHECK_INTERVAL:=1}"
if [ "${DEV}" = 1 ]; then
NATS_CONFIG=/workspace/api/tacticalrmm/nats-rmm.conf
NATS_API_CONFIG=/workspace/api/tacticalrmm/nats-api.conf
else
NATS_CONFIG="${TACTICAL_DIR}/api/nats-rmm.conf"
NATS_API_CONFIG="${TACTICAL_DIR}/api/nats-api.conf"
fi
sleep 15
@@ -16,6 +19,27 @@ until [ -f "${TACTICAL_READY_FILE}" ]; do
sleep 10
done
config_watcher="$(cat << EOF
while true; do
sleep ${NATS_CONFIG_CHECK_INTERVAL};
if [[ ! -z \${NATS_CHECK} ]]; then
NATS_RELOAD=\$(date -r '${NATS_CONFIG}')
if [[ \$NATS_RELOAD == \$NATS_CHECK ]]; then
:
else
nats-server --signal reload;
NATS_CHECK=\$(date -r '${NATS_CONFIG}');
fi
else NATS_CHECK=\$(date -r '${NATS_CONFIG}');
fi
done
EOF
)"
echo "${config_watcher}" > /usr/local/bin/config_watcher.sh
chmod +x /usr/local/bin/config_watcher.sh
mkdir -p /var/log/supervisor
mkdir -p /etc/supervisor/conf.d
@@ -32,7 +56,16 @@ stdout_logfile_maxbytes=0
redirect_stderr=true
[program:config-watcher]
command=/bin/bash -c "inotifywait -mq -e modify "${NATS_CONFIG}" | while read event; do nats-server --signal reload; done;"
command=/bin/bash /usr/local/bin/config_watcher.sh
startsecs=10
autorestart=true
startretries=1
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
[program:nats-api]
command=/bin/bash -c "/usr/local/bin/nats-api -config ${NATS_API_CONFIG}"
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true

View File

@@ -5,10 +5,15 @@ set -e
: "${WORKER_CONNECTIONS:=2048}"
: "${APP_PORT:=80}"
: "${API_PORT:=80}"
: "${NGINX_RESOLVER:=127.0.0.11}"
: "${BACKEND_SERVICE:=tactical-backend}"
: "${FRONTEND_SERVICE:=tactical-frontend}"
: "${MESH_SERVICE:=tactical-meshcentral}"
: "${WEBSOCKETS_SERVICE:=tactical-websockets}"
: "${DEV:=0}"
CERT_PRIV_PATH=${TACTICAL_DIR}/certs/privkey.pem
CERT_PUB_PATH=${TACTICAL_DIR}/certs/fullchain.pem
: "${CERT_PRIV_PATH:=${TACTICAL_DIR}/certs/privkey.pem}"
: "${CERT_PUB_PATH:=${TACTICAL_DIR}/certs/fullchain.pem}"
mkdir -p "${TACTICAL_DIR}/certs"
@@ -34,7 +39,7 @@ fi
if [[ $DEV -eq 1 ]]; then
API_NGINX="
#Using variable to disable start checks
set \$api http://tactical-backend:${API_PORT};
set \$api http://${BACKEND_SERVICE}:${API_PORT};
proxy_pass \$api;
proxy_http_version 1.1;
proxy_cache_bypass \$http_upgrade;
@@ -51,7 +56,7 @@ if [[ $DEV -eq 1 ]]; then
else
API_NGINX="
#Using variable to disable start checks
set \$api tactical-backend:${API_PORT};
set \$api ${BACKEND_SERVICE}:${API_PORT};
include uwsgi_params;
uwsgi_pass \$api;
@@ -61,7 +66,7 @@ fi
nginx_config="$(cat << EOF
# backend config
server {
resolver 127.0.0.11 valid=30s;
resolver ${NGINX_RESOLVER} valid=30s;
server_name ${API_HOST};
@@ -80,7 +85,7 @@ server {
}
location ~ ^/ws/ {
set \$api http://tactical-websockets:8383;
set \$api http://${WEBSOCKETS_SERVICE}:8383;
proxy_pass \$api;
proxy_http_version 1.1;
@@ -111,13 +116,13 @@ server {
# frontend config
server {
resolver 127.0.0.11 valid=30s;
resolver ${NGINX_RESOLVER} valid=30s;
server_name ${APP_HOST};
location / {
#Using variable to disable start checks
set \$app http://tactical-frontend:${APP_PORT};
set \$app http://${FRONTEND_SERVICE}:${APP_PORT};
proxy_pass \$app;
proxy_http_version 1.1;
@@ -149,7 +154,7 @@ server {
# meshcentral config
server {
resolver 127.0.0.11 valid=30s;
resolver ${NGINX_RESOLVER} valid=30s;
listen 443 ssl;
proxy_send_timeout 330s;
@@ -163,7 +168,7 @@ server {
location / {
#Using variable to disable start checks
set \$meshcentral http://tactical-meshcentral:443;
set \$meshcentral http://${MESH_SERVICE}:443;
proxy_pass \$meshcentral;
proxy_http_version 1.1;
@@ -180,7 +185,7 @@ server {
}
server {
resolver 127.0.0.11 valid=30s;
resolver ${NGINX_RESOLVER} valid=30s;
listen 80;
server_name ${MESH_HOST};

View File

@@ -1,5 +1,5 @@
# creates python virtual env
FROM python:3.9.6-slim AS CREATE_VENV_STAGE
FROM python:3.9.9-slim AS CREATE_VENV_STAGE
ARG DEBIAN_FRONTEND=noninteractive
@@ -23,7 +23,7 @@ RUN apt-get update && \
# runtime image
FROM python:3.9.6-slim
FROM python:3.9.9-slim
# set env variables
ENV VIRTUAL_ENV /opt/venv
@@ -50,10 +50,6 @@ RUN apt-get update && \
SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"]
# copy nats-api file
COPY natsapi/bin/nats-api /usr/local/bin/
RUN chmod +x /usr/local/bin/nats-api
# docker init
COPY docker/containers/tactical/entrypoint.sh /
RUN chmod +x /entrypoint.sh

View File

@@ -9,7 +9,8 @@ set -e
: "${POSTGRES_USER:=tactical}"
: "${POSTGRES_PASS:=tactical}"
: "${POSTGRES_DB:=tacticalrmm}"
: "${MESH_CONTAINER:=tactical-meshcentral}"
: "${MESH_SERVICE:=tactical-meshcentral}"
: "${MESH_WS_URL:=ws://${MESH_SERVICE}:443}"
: "${MESH_USER:=meshcentral}"
: "${MESH_PASS:=meshcentralpass}"
: "${MESH_HOST:=tactical-meshcentral}"
@@ -17,6 +18,8 @@ set -e
: "${APP_HOST:=tactical-frontend}"
: "${REDIS_HOST:=tactical-redis}"
: "${CERT_PRIV_PATH:=${TACTICAL_DIR}/certs/privkey.pem}"
: "${CERT_PUB_PATH:=${TACTICAL_DIR}/certs/fullchain.pem}"
function check_tactical_ready {
sleep 15
@@ -44,7 +47,7 @@ if [ "$1" = 'tactical-init' ]; then
sleep 5
done
until (echo > /dev/tcp/"${MESH_CONTAINER}"/443) &> /dev/null; do
until (echo > /dev/tcp/"${MESH_SERVICE}"/443) &> /dev/null; do
echo "waiting for meshcentral container to be ready..."
sleep 5
done
@@ -61,8 +64,8 @@ DEBUG = False
DOCKER_BUILD = True
CERT_FILE = '/opt/tactical/certs/fullchain.pem'
KEY_FILE = '/opt/tactical/certs/privkey.pem'
CERT_FILE = '${CERT_PUB_PATH}'
KEY_FILE = '${CERT_PRIV_PATH}'
EXE_DIR = '/opt/tactical/api/tacticalrmm/private/exe'
LOG_DIR = '/opt/tactical/api/tacticalrmm/private/log'
@@ -92,7 +95,7 @@ MESH_USERNAME = '${MESH_USER}'
MESH_SITE = 'https://${MESH_HOST}'
MESH_TOKEN_KEY = '${MESH_TOKEN}'
REDIS_HOST = '${REDIS_HOST}'
MESH_WS_URL = 'ws://${MESH_CONTAINER}:443'
MESH_WS_URL = '${MESH_WS_URL}'
ADMIN_ENABLED = False
EOF
)"
@@ -129,7 +132,9 @@ EOF
python manage.py load_chocos
python manage.py load_community_scripts
python manage.py reload_nats
python manage.py create_natsapi_conf
python manage.py create_installer_user
python manage.py post_update_tasks
# create super user
echo "from accounts.models import User; User.objects.create_superuser('${TRMM_USER}', 'admin@example.com', '${TRMM_PASS}') if not User.objects.filter(username='${TRMM_USER}').exists() else 0;" | python manage.py shell
@@ -167,4 +172,4 @@ if [ "$1" = 'tactical-websockets' ]; then
export DJANGO_SETTINGS_MODULE=tacticalrmm.settings
daphne tacticalrmm.asgi:application --port 8383 -b 0.0.0.0
fi
fi

View File

@@ -8,17 +8,16 @@ networks:
driver: default
config:
- subnet: 172.20.0.0/24
api-db:
redis:
mesh-db:
api-db: null
redis: null
mesh-db: null # docker managed persistent volumes
# docker managed persistent volumes
volumes:
tactical_data:
postgres_data:
mongo_data:
mesh_data:
redis_data:
tactical_data: null
postgres_data: null
mongo_data: null
mesh_data: null
redis_data: null
services:
# postgres database for api service
@@ -41,7 +40,7 @@ services:
image: redis:6.0-alpine
command: redis-server --appendonly yes
restart: always
volumes:
volumes:
- redis_data:/data
networks:
- redis
@@ -51,7 +50,7 @@ services:
container_name: trmm-init
image: ${IMAGE_REPO}tactical:${VERSION}
restart: on-failure
command: ["tactical-init"]
command: [ "tactical-init" ]
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASS: ${POSTGRES_PASS}
@@ -63,13 +62,13 @@ services:
TRMM_PASS: ${TRMM_PASS}
depends_on:
- tactical-postgres
- tactical-meshcentral
- tactical-meshcentral
networks:
- api-db
- proxy
volumes:
- tactical_data:/opt/tactical
# nats
tactical-nats:
container_name: trmm-nats
@@ -82,6 +81,7 @@ services:
volumes:
- tactical_data:/opt/tactical
networks:
api-db: null
proxy:
aliases:
- ${API_HOST}
@@ -91,7 +91,7 @@ services:
container_name: trmm-meshcentral
image: ${IMAGE_REPO}tactical-meshcentral:${VERSION}
restart: always
environment:
environment:
MESH_HOST: ${MESH_HOST}
MESH_USER: ${MESH_USER}
MESH_PASS: ${MESH_PASS}
@@ -102,7 +102,7 @@ services:
proxy:
aliases:
- ${MESH_HOST}
mesh-db:
mesh-db: null
volumes:
- tactical_data:/opt/tactical
- mesh_data:/home/node/app/meshcentral-data
@@ -137,7 +137,7 @@ services:
tactical-backend:
container_name: trmm-backend
image: ${IMAGE_REPO}tactical:${VERSION}
command: ["tactical-backend"]
command: [ "tactical-backend" ]
restart: always
networks:
- proxy
@@ -152,7 +152,7 @@ services:
tactical-websockets:
container_name: trmm-websockets
image: ${IMAGE_REPO}tactical:${VERSION}
command: ["tactical-websockets"]
command: [ "tactical-websockets" ]
restart: always
networks:
- proxy
@@ -188,7 +188,7 @@ services:
tactical-celery:
container_name: trmm-celery
image: ${IMAGE_REPO}tactical:${VERSION}
command: ["tactical-celery"]
command: [ "tactical-celery" ]
restart: always
networks:
- redis
@@ -204,7 +204,7 @@ services:
tactical-celerybeat:
container_name: trmm-celerybeat
image: ${IMAGE_REPO}tactical:${VERSION}
command: ["tactical-celerybeat"]
command: [ "tactical-celerybeat" ]
restart: always
networks:
- proxy

View File

@@ -42,7 +42,7 @@ Navigate to an agent with ConnectWise Service running (or apply using **Settings
Go to Tasks.</br>
Add Task</br>
**Select Script** = `ScreenConnect - Get GUID for client` (this is a builtin script from script library)</br>
**Script argument** = `-serviceName{{client.ScreenConnectService}}`</br>
**Script argument** = `-serviceName {{client.ScreenConnectService}}`</br>
**Descriptive name of task** = `Collects the Machine GUID for ScreenConnect.`</br>
**Collector Task** = `CHECKED`</br>
**Custom Field to update** = `ScreenConectGUID`</br>
@@ -61,6 +61,12 @@ It should ask you to sign into your Connectwise Control server if you are not al
*****
## Install Screenconnect via Tactical
Use the [Screenconnect AIO script](https://github.com/wh1te909/tacticalrmm/blob/develop/scripts/Win_ScreenConnectAIO.ps1)
![AIO](images/3rdparty_sc_aio.png)
## Install Tactical RMM via Screeconnect commands window
1. Create a Deplopment under **Agents > Manage Deployments**

View File

@@ -0,0 +1,42 @@
# Splashtop
## Splashtop Integration
From the UI go to **Settings > Global Settings > CUSTOM FIELDS > Agents**
Add Custom Field</br>
**Target** = `Agent`</br>
**Name** = `SplashtopSUUID`</br>
**Field Type** = `Text`</br>
![Service Name](images/3rdparty_splashtop1.png)
While in Global Settings go to **URL ACTIONS**
Add a URL Action</br>
**Name** = `Splashtop`</br>
**Description** = `Connect to a Splashtop client`</br>
**URL Pattern** =
```html
st-business://com.splashtop.business?account=&uuid={{agent.SplashtopSUUID}}&sessiontype=remote
```
Navigate to an agent with Splashtop running (or apply using **Settings > Automation Manager**).</br>
Go to Tasks.</br>
Add Task</br>
**Select Script** = `Splashtop - Get SUUID for client` (this is a builtin script from script library)</br>
**Descriptive name of task** = `Obtain Splashtop SUUID from device registry.`</br>
**Collector Task** = `CHECKED`</br>
**Custom Field to update** = `SplashtopSUUID`</br>
![Service Name](images/3rdparty_splashtop2.png)
Click **Next**</br>
Check **Manual**</br>
Click **Add Task**
Right click on the newly created task and click **Run Task Now**.
Give it a second to execute then right click the agent that you are working with and go to **Run URL Action > Splashtop**

144
docs/docs/av.md Normal file
View File

@@ -0,0 +1,144 @@
# Antivirus
They are usually fraught with false-positives because we live in a world of complex greys, not black and white.
At the moment, Microsoft Windows Defender thinks a go executable with virtually nothing in it is the "Trojan:Win32/Wacatac.B!ml" virus <https://old.reddit.com/r/golang/comments/s1bh01/goexecutables_and_windows_defender/>
At Tactical we recommend:
1. No 3rd party AV
2. Use the `Defender Status Report` script (Task > Run Daily - Use Automation manager) to monitor machines: <https://github.com/wh1te909/tacticalrmm/blob/develop/scripts/Win_Defender_Status_Report.ps1>
3. If you want to lock a system down, run the `Defender Enable` script (test in your environment, because it can stop Microsoft Office from opening docs) that will turn on Protected Folders: <https://github.com/wh1te909/tacticalrmm/blob/develop/scripts/Win_Defender_Enable.ps1> and you will be extremely safe. Annoyed, but safe. Use [this](https://github.com/amidaware/trmm-awesome/blob/main/scripts/Windows_Defender_Allowed_List.ps1) as an Exclusion List for Protected Folders items.
Be aware there is also [a powershell script](https://github.com/wh1te909/tacticalrmm/blob/develop/scripts/Win_TRMM_AV_Update_Exclusion.ps1) to add TRMM exclusions specific to Windows Defender
!!!note
If you need to use 3rd party AV, add the necessary exclusions (see below for examples) and submit the exe's as safe
## Bitdefender Gravityzone
Admin URL: <https://cloud.gravityzone.bitdefender.com/>
To exclude URLs: Policies > {policy name} > Network Protection > Content Control > Settings > Exclusions
![Web Exclusions](images/avbitdefender_gravityzone_exclusions0.png)
![Web Exclusions](images/avbitdefender_gravityzone_exclusions1.png)
![Web Exclusions](images/avbitdefender_gravityzone_exclusions2.png)
## Webroot
Admin URL:
![Web Exclusions](images/avwebroot.png)
![Web Exclusions](images/avwebroot5.png)
![Web Exclusions](images/avwebroot4.png)
![Web Exclusions](images/avwebroot3.png)
![Web Exclusions](images/avwebroot2.png)
![Web Exclusions](images/avwebroot1.png)
## Sophos
### Sophos Central Admin
Go To Global Settings >> General >> Global Exclusions >> Add Exclusion
![Agent Exclusions](images/sophoscascreen1.png)
![Agent Exclusions](images/sophoscascreen2.png)
![Agent Exclusions](images/sophoscascreen3.png)
![Agent Exclusions](images/sophoscascreen4.png)
![Agent Exclusions](images/sophoscascreen5.png)
![Agent Exclusions](images/sophoscascreen6.png)
![Agent Exclusions](images/sophoscascreen7.png)
### Sophos XG Firewall
![Agent Exclusions](images/sophoscascreen1.png)
Log into Sophos Central Admin
Admin URL: <https://cloud.sophos.com/>
Log into the Sophos XG Firewall
Go To System >> Hosts and services >> FQDN Host Group and create a new group
![FW Exclusions](images/sophosxgscreen1.png)
Go To System >> Hosts and services >> FQDN Host
Create the following 3 hosts and add each to your FQDN host group.
- api.yourdomain.com
- mesh.yourdomain.com
- rmm.yourdomain.com (Optional if you want your client to have GUI access to Tactical RMM)
![FW Exclusions](images/sophosxgscreen2.png)
![FW Exclusions](images/sophosxgscreen3.png)
Go To Hosts and services >> Services and create the following services
- Name: Tactical-Service-4222
- Protocol: TCP
- Source port: 1:65535
- Destination port: 4222
- Name: Tactical-Service-443
- Protocol: TCP
- Source port: 1:65535
- Destination port: 443
![FW Exclusions](images/sophosxgscreen4.png)
![FW Exclusions](images/sophosxgscreen5.png)
Go To Hosts and services >> Service group and create the following service group
![FW Exclusions](images/sophosxgscreen6.png)
Go To Protect >> Rules and policies and add a firewall rule
- Rule name: Tactical Rule
- Rule position: Top
- Source zones: LAN
- Source networks: ANY
- Destination zones: WAN
- Destination networks: Your FQDN Host Group
- Services: Tactical Services
![FW Exclusions](images/sophosxgscreen7.png)
![FW Exclusions](images/sophosxgscreen8.png)
Optionally select Log Firewall Traffic checkbox for troubleshooting.
## ESET ESMC Console
There are two spots:
1. In the Detection Engine -> Performance Exclusions
2. Web Access Protection -> URL Address Management
![Web Exclusions](images/esetesmc1.png)
![Web Exclusions](images/esetesmc2.png)
![Web Exclusions](images/esetesmc3.png)
![Web Exclusions](images/esetesmc4.png)
![Web Exclusions](images/esetesmc5.png)

View File

@@ -1,4 +1,4 @@
# Backing up the RMM
## Backing up the RMM
!!!note
This is only applicable for the standard install, not Docker installs.
@@ -27,3 +27,21 @@ chmod +x backup.sh
The backup tar file will be saved in `/rmmbackups` with the following format:
`rmm-backup-CURRENTDATETIME.tar`
## Schedule to run daily via cron
Make a symlink in `/etc/cron.d` (daily cron jobs) with these contents `00 18 * * * tactical /rmm/backup.sh` to run at 6pm daily.
```bash
echo -e "\n" >> /rmm/backup.sh
sudo ln -s /rmm/backup.sh /etc/cron.daily/
```
!!!warning
Currently the backup script doesn't have any pruning functions so the folder will grow forever without periodic cleanup
## Video Walkthru
<div class="video-wrapper">
<iframe width="320" height="180" src="https://www.youtube.com/embed/rC0NgYJUf_8" frameborder="0" allowfullscreen></iframe>
</div>

View File

@@ -4,7 +4,7 @@
Tactical RMM agents are now [code signed](https://comodosslstore.com/resources/what-is-microsoft-authenticode-code-signing-certificate/)!
To get access to code signed agents, you must be a [Github Sponsor](https://github.com/sponsors/wh1te909) with a minumum monthly donation of $50.00
To get access to code signed agents, you must be a [Github Sponsor](https://github.com/sponsors/wh1te909) with a minumum **monthly** donation of $50.00. If you signup for the $50, and then downgrade your auth token _**will be**_ invalidated and stop working.
Once you have become a sponsor, please email **support@amidaware.com** with your Github username (and Discord username if you're on our [Discord](https://discord.gg/upGTkWp))

View File

@@ -87,7 +87,8 @@ npm install -g @quasar/cli
quasar dev
```
!!!info If you receive a CORS error when trying to log into your server via localhost or IP, try the following
!!!info
If you receive a CORS error when trying to log into your server via localhost or IP, try the following
```bash
rm -rf node_modules .quasar
npm install

Some files were not shown because too many files have changed in this diff Show More