dissect.target 3.20.dev5__py3-none-any.whl → 3.20.dev7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dissect/target/helpers/record.py +1 -1
- dissect/target/loader.py +0 -1
- dissect/target/loaders/mqtt.py +93 -11
- dissect/target/tools/query.py +0 -3
- dissect/target/tools/utils.py +0 -4
- {dissect.target-3.20.dev5.dist-info → dissect.target-3.20.dev7.dist-info}/METADATA +1 -1
- {dissect.target-3.20.dev5.dist-info → dissect.target-3.20.dev7.dist-info}/RECORD +12 -14
- dissect/target/helpers/targetd.py +0 -58
- dissect/target/loaders/targetd.py +0 -223
- {dissect.target-3.20.dev5.dist-info → dissect.target-3.20.dev7.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.20.dev5.dist-info → dissect.target-3.20.dev7.dist-info}/LICENSE +0 -0
- {dissect.target-3.20.dev5.dist-info → dissect.target-3.20.dev7.dist-info}/WHEEL +0 -0
- {dissect.target-3.20.dev5.dist-info → dissect.target-3.20.dev7.dist-info}/entry_points.txt +0 -0
- {dissect.target-3.20.dev5.dist-info → dissect.target-3.20.dev7.dist-info}/top_level.txt +0 -0
dissect/target/helpers/record.py
CHANGED
@@ -144,7 +144,6 @@ EmptyRecord = RecordDescriptor(
|
|
144
144
|
)
|
145
145
|
|
146
146
|
COMMON_INTERFACE_ELEMENTS = [
|
147
|
-
("string", "source"),
|
148
147
|
("string", "name"),
|
149
148
|
("string", "type"),
|
150
149
|
("boolean", "enabled"),
|
@@ -152,6 +151,7 @@ COMMON_INTERFACE_ELEMENTS = [
|
|
152
151
|
("net.ipaddress[]", "dns"),
|
153
152
|
("net.ipaddress[]", "ip"),
|
154
153
|
("net.ipaddress[]", "gateway"),
|
154
|
+
("string", "source"),
|
155
155
|
]
|
156
156
|
|
157
157
|
|
dissect/target/loader.py
CHANGED
@@ -177,7 +177,6 @@ def open(item: Union[str, Path], *args, **kwargs) -> Loader:
|
|
177
177
|
register("local", "LocalLoader")
|
178
178
|
register("remote", "RemoteLoader")
|
179
179
|
register("mqtt", "MQTTLoader")
|
180
|
-
register("targetd", "TargetdLoader")
|
181
180
|
register("asdf", "AsdfLoader")
|
182
181
|
register("tar", "TarLoader")
|
183
182
|
register("vmx", "VmxLoader")
|
dissect/target/loaders/mqtt.py
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
import argparse
|
3
4
|
import atexit
|
4
5
|
import logging
|
5
6
|
import math
|
6
7
|
import os
|
8
|
+
import re
|
7
9
|
import ssl
|
8
10
|
import sys
|
9
11
|
import time
|
@@ -280,7 +282,7 @@ class Broker:
|
|
280
282
|
factor = 1
|
281
283
|
|
282
284
|
def __init__(
|
283
|
-
self, broker:
|
285
|
+
self, broker: str, port: str, key: str, crt: str, ca: str, case: str, username: str, password: str, **kwargs
|
284
286
|
):
|
285
287
|
self.broker_host = broker
|
286
288
|
self.broker_port = int(port)
|
@@ -352,8 +354,7 @@ class Broker:
|
|
352
354
|
log.error(f"Failed to decode payload for hostname {hostname}: {e}")
|
353
355
|
return
|
354
356
|
|
355
|
-
|
356
|
-
print(f'"{hostname}",{decoded_payload}')
|
357
|
+
print(decoded_payload)
|
357
358
|
|
358
359
|
def _on_log(self, client: mqtt.Client, userdata: Any, log_level: int, message: str) -> None:
|
359
360
|
log.debug(message)
|
@@ -423,16 +424,97 @@ class Broker:
|
|
423
424
|
self.mqtt_client.loop_start()
|
424
425
|
|
425
426
|
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
427
|
+
def strictly_positive(value: str) -> int:
|
428
|
+
"""
|
429
|
+
Validates that the provided value is a strictly positive integer.
|
430
|
+
|
431
|
+
This function is intended to be used as a type for argparse arguments.
|
432
|
+
|
433
|
+
Args:
|
434
|
+
value (str): The value to validate.
|
435
|
+
|
436
|
+
Returns:
|
437
|
+
int: The validated integer value.
|
438
|
+
|
439
|
+
Raises:
|
440
|
+
argparse.ArgumentTypeError: If the value is not a strictly positive integer.
|
441
|
+
"""
|
442
|
+
try:
|
443
|
+
strictly_positive_value = int(value)
|
444
|
+
if strictly_positive_value < 1:
|
445
|
+
raise argparse.ArgumentTypeError("Value must be larger than or equal to 1.")
|
446
|
+
return strictly_positive_value
|
447
|
+
except ValueError:
|
448
|
+
raise argparse.ArgumentTypeError(f"Invalid integer value specified: '{value}'")
|
449
|
+
|
450
|
+
|
451
|
+
def port(value: str) -> int:
|
452
|
+
"""
|
453
|
+
Convert a string value to an integer representing a valid port number.
|
454
|
+
|
455
|
+
This function is intended to be used as a type for argparse arguments.
|
456
|
+
|
457
|
+
Args:
|
458
|
+
value (str): The string representation of the port number.
|
459
|
+
Returns:
|
460
|
+
int: The port number as an integer.
|
461
|
+
Raises:
|
462
|
+
argparse.ArgumentTypeError: If the port number is not an integer or out of the valid range (1-65535).
|
463
|
+
"""
|
464
|
+
|
465
|
+
try:
|
466
|
+
port = int(value)
|
467
|
+
if port < 1 or port > 65535:
|
468
|
+
raise argparse.ArgumentTypeError("Port number must be between 1 and 65535.")
|
469
|
+
return port
|
470
|
+
except ValueError:
|
471
|
+
raise argparse.ArgumentTypeError(f"Invalid port number specified: '{value}'")
|
472
|
+
|
473
|
+
|
474
|
+
def case(value: str) -> str:
|
475
|
+
"""
|
476
|
+
Validates that the given value is a valid case name consisting of
|
477
|
+
alphanumeric characters and underscores only.
|
478
|
+
|
479
|
+
This function is intended to be used as a type for argparse arguments.
|
480
|
+
|
481
|
+
Args:
|
482
|
+
value (str): The case name to validate.
|
483
|
+
|
484
|
+
Returns:
|
485
|
+
str: The validated case name if it matches the required pattern.
|
486
|
+
|
487
|
+
Raises:
|
488
|
+
argparse.ArgumentTypeError: If the case name does not match the required pattern.
|
489
|
+
"""
|
490
|
+
|
491
|
+
if re.match(r"^[a-zA-Z0-9_]+$", value):
|
492
|
+
return value
|
493
|
+
|
494
|
+
raise argparse.ArgumentTypeError(f"Invalid case name specified: '{value}'")
|
495
|
+
|
496
|
+
|
497
|
+
@arg(
|
498
|
+
"--mqtt-peers",
|
499
|
+
type=strictly_positive,
|
500
|
+
dest="peers",
|
501
|
+
default=1,
|
502
|
+
help="minimum number of peers to await for first alias",
|
503
|
+
)
|
504
|
+
@arg(
|
505
|
+
"--mqtt-case",
|
506
|
+
type=case,
|
507
|
+
dest="case",
|
508
|
+
help="case name (broker will determine if you are allowed to access this data)",
|
509
|
+
)
|
510
|
+
@arg("--mqtt-port", type=port, dest="port", default=443, help="broker connection port")
|
511
|
+
@arg("--mqtt-broker", default="localhost", dest="broker", help="broker ip-address")
|
512
|
+
@arg("--mqtt-key", type=Path, dest="key", required=True, help="private key file")
|
513
|
+
@arg("--mqtt-crt", type=Path, dest="crt", required=True, help="client certificate file")
|
514
|
+
@arg("--mqtt-ca", type=Path, dest="ca", required=True, help="certificate authority file")
|
433
515
|
@arg("--mqtt-command", dest="command", help="direct command to client(s)")
|
434
516
|
@arg("--mqtt-diag", action="store_true", dest="diag", help="show MQTT diagnostic information")
|
435
|
-
@arg("--mqtt-username", dest="username", help="Username for connection")
|
517
|
+
@arg("--mqtt-username", dest="username", default="mqtt-loader", help="Username for connection")
|
436
518
|
@arg("--mqtt-password", action="store_true", dest="password", help="Ask for password before connecting")
|
437
519
|
class MQTTLoader(Loader):
|
438
520
|
"""Load remote targets through a broker."""
|
dissect/target/tools/query.py
CHANGED
@@ -18,7 +18,6 @@ from dissect.target.exceptions import (
|
|
18
18
|
UnsupportedPluginError,
|
19
19
|
)
|
20
20
|
from dissect.target.helpers import cache, record_modifier
|
21
|
-
from dissect.target.loaders.targetd import ProxyLoader
|
22
21
|
from dissect.target.plugin import PLUGINS, OSPlugin, Plugin, find_plugin_functions
|
23
22
|
from dissect.target.report import ExecutionReport
|
24
23
|
from dissect.target.tools.utils import (
|
@@ -174,8 +173,6 @@ def main():
|
|
174
173
|
|
175
174
|
if targets:
|
176
175
|
for plugin_target in Target.open_all(targets, args.children):
|
177
|
-
if isinstance(plugin_target._loader, ProxyLoader):
|
178
|
-
parser.error("can't list compatible plugins for remote targets.")
|
179
176
|
funcs, _ = find_plugin_functions(plugin_target, args.list, compatibility=True, show_hidden=True)
|
180
177
|
for func in funcs:
|
181
178
|
collected_plugins[func.path] = func.plugin_desc
|
dissect/target/tools/utils.py
CHANGED
@@ -16,7 +16,6 @@ from dissect.target import Target
|
|
16
16
|
from dissect.target.exceptions import UnsupportedPluginError
|
17
17
|
from dissect.target.helpers import docs, keychain
|
18
18
|
from dissect.target.helpers.docs import get_docstring
|
19
|
-
from dissect.target.helpers.targetd import CommandProxy
|
20
19
|
from dissect.target.loader import LOADERS_BY_SCHEME
|
21
20
|
from dissect.target.plugin import (
|
22
21
|
OSPlugin,
|
@@ -250,9 +249,6 @@ def plugin_function_with_argparser(
|
|
250
249
|
|
251
250
|
plugin_method = plugin_obj.get_all_records
|
252
251
|
parser = generate_argparse_for_plugin(plugin_obj)
|
253
|
-
elif isinstance(target_attr, CommandProxy):
|
254
|
-
plugin_method = target_attr.command()
|
255
|
-
parser = generate_argparse_for_bound_method(plugin_method)
|
256
252
|
elif callable(target_attr):
|
257
253
|
plugin_method = target_attr
|
258
254
|
parser = generate_argparse_for_bound_method(target_attr)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: dissect.target
|
3
|
-
Version: 3.20.
|
3
|
+
Version: 3.20.dev7
|
4
4
|
Summary: This module ties all other Dissect modules together, it provides a programming API and command line tools which allow easy access to various data sources inside disk images or file collections (a.k.a. targets)
|
5
5
|
Author-email: Dissect Team <dissect@fox-it.com>
|
6
6
|
License: Affero General Public License v3
|
@@ -2,7 +2,7 @@ dissect/target/__init__.py,sha256=Oc7ounTgq2hE4nR6YcNabetc7SQA40ldSa35VEdZcQU,63
|
|
2
2
|
dissect/target/container.py,sha256=0YcwcGmfJjhPXUB6DEcjWEoSuAtTDxMDpoTviMrLsxM,9353
|
3
3
|
dissect/target/exceptions.py,sha256=ULi7NXlqju_d8KENEL3aimmfKTFfbNssfeWhAnOB654,2972
|
4
4
|
dissect/target/filesystem.py,sha256=__p2p72B6mKgIiAOj85EC3ESdZ8gjgkm2pt1KKym3h0,60743
|
5
|
-
dissect/target/loader.py,sha256=
|
5
|
+
dissect/target/loader.py,sha256=11B3W9IpfdTU9GJiKONGffuGeICog7J9ypXZ6mffsBE,7337
|
6
6
|
dissect/target/plugin.py,sha256=xbvmjZMFNwJXZYMBE4KI93-nWL9Zhum8x39ydcO6s2o,50578
|
7
7
|
dissect/target/report.py,sha256=06uiP4MbNI8cWMVrC1SasNS-Yg6ptjVjckwj8Yhe0Js,7958
|
8
8
|
dissect/target/target.py,sha256=m4bAKgPLUJERKgxRZFevKvEBNaz77wIC5mVrDe6eI8o,32438
|
@@ -61,12 +61,11 @@ dissect/target/helpers/mui.py,sha256=i-7XoHbu4WO2fYapK9yGAMW04rFlgRispknc1KQIS5Q
|
|
61
61
|
dissect/target/helpers/network_managers.py,sha256=ByBSe2K3c8hgQC6dokcf-hHdmPcD8PmrOj0xs1C3yhs,25743
|
62
62
|
dissect/target/helpers/polypath.py,sha256=h8p7m_OCNiQljGwoZh5Aflr9H2ot6CZr6WKq1OSw58o,2175
|
63
63
|
dissect/target/helpers/protobuf.py,sha256=b4DsnqrRLrefcDjx7rQno-_LBcwtJXxuKf5RdOegzfE,1537
|
64
|
-
dissect/target/helpers/record.py,sha256=
|
64
|
+
dissect/target/helpers/record.py,sha256=vbsZBUQ5sxsWGaGUJk2yHV5miXvK9HfZgVarM5Cmqto,5852
|
65
65
|
dissect/target/helpers/record_modifier.py,sha256=O_Jj7zOi891HIyAYjxxe6LFPYETHdMa5lNjo4NA_T_w,3969
|
66
66
|
dissect/target/helpers/regutil.py,sha256=kX-sSZbW8Qkg29Dn_9zYbaQrwLumrr4Y8zJ1EhHXIAM,27337
|
67
67
|
dissect/target/helpers/shell_application_ids.py,sha256=hYxrP-YtHK7ZM0ectJFHfoMB8QUXLbYNKmKXMWLZRlA,38132
|
68
68
|
dissect/target/helpers/shell_folder_ids.py,sha256=Behhb8oh0kMxrEk6YYKYigCDZe8Hw5QS6iK_d2hTs2Y,24978
|
69
|
-
dissect/target/helpers/targetd.py,sha256=ELhUulzQ4OgXgHsWhsLgM14vut8Wm6btr7qTynlwKaE,1812
|
70
69
|
dissect/target/helpers/utils.py,sha256=K3xVq9D0FwIhTBAuiWN8ph7Pq2GABgG3hOz-3AmKuEA,4244
|
71
70
|
dissect/target/helpers/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
72
71
|
dissect/target/helpers/compat/path_310.py,sha256=PsLDIodlp3Hv5u-w7GDl6_LnTtchBYcRjz2MicX1egg,16982
|
@@ -88,7 +87,7 @@ dissect/target/loaders/kape.py,sha256=t5TfrGLqPeIpUUpXzIl6aHsqXMEGDqJ5YwDCs07DiB
|
|
88
87
|
dissect/target/loaders/libvirt.py,sha256=_3EFIytMGbiLMISHx4QXVrDebsRO6J6sMkE3TH68qsg,1374
|
89
88
|
dissect/target/loaders/local.py,sha256=Ul-LCd_fY7SyWOVR6nH-NqbkuNpxoZVmffwrkvQElU8,16453
|
90
89
|
dissect/target/loaders/log.py,sha256=cCkDIRS4aPlX3U-n_jUKaI2FPSV3BDpfqKceaU7rBbo,1507
|
91
|
-
dissect/target/loaders/mqtt.py,sha256=
|
90
|
+
dissect/target/loaders/mqtt.py,sha256=WVNXDCzEluyyUidXr25-H9Ic-c75dZIRdDdL5sa4yVQ,19375
|
92
91
|
dissect/target/loaders/multiraw.py,sha256=4a3ZST0NwjnfPDxHkcEfAcX2ddUlT_C-rcrMHNg1wp4,1046
|
93
92
|
dissect/target/loaders/ova.py,sha256=6h4O-7i87J394C6KgLsPkdXRAKNwtPubzLNS3vBGs7U,744
|
94
93
|
dissect/target/loaders/overlay.py,sha256=tj99HKvNG5_JbGfb1WCv4KNSbXXSnEcPQY5XT-JUxn8,992
|
@@ -104,7 +103,6 @@ dissect/target/loaders/smb.py,sha256=qP8m4Jq7hvAvUCF9jB4yr2Zut7p_R02_vxziNN3R1to
|
|
104
103
|
dissect/target/loaders/tanium.py,sha256=P9euiQzvVaQQtMQlEmNe0V25w1BkQFRZBuS-0-ksHpY,1585
|
105
104
|
dissect/target/loaders/tar.py,sha256=2uF9-mmFbSdLsCkySfT9AkzagQXsTQvDjrdL1UyjFuI,4170
|
106
105
|
dissect/target/loaders/target.py,sha256=MU_HUtg58YdhdZu6ga1sYG7fK61Dn7N0TBkWXDCWwyc,798
|
107
|
-
dissect/target/loaders/targetd.py,sha256=sfbn2_j3il2G-rPywAoNT5YPtD5KmKkmBv1zrPDRs6I,8250
|
108
106
|
dissect/target/loaders/utm.py,sha256=7oHYP_jmr5gcjoyOP1pnh9Rz-IqQirBI6bjSvGwiKao,1053
|
109
107
|
dissect/target/loaders/vb.py,sha256=CdimOMeoJEDq8xYDgtldGSiwhR-dY5uxac1L0sYwAEU,2078
|
110
108
|
dissect/target/loaders/vbox.py,sha256=8JD7D8iAY9JRvTHsrosp5ZMsZezuLhZ10Zt8sEL7KBI,732
|
@@ -348,10 +346,10 @@ dissect/target/tools/fsutils.py,sha256=dyAdp2fzydcozaIZ1mFTpdUeVcibYNJCHN8AFw5Fo
|
|
348
346
|
dissect/target/tools/info.py,sha256=8nnbqFUYeo4NLPE7ORcTBcDL-TioGB2Nqc1TKcu5qdY,5715
|
349
347
|
dissect/target/tools/logging.py,sha256=5ZnumtMWLyslxfrUGZ4ntRyf3obOOhmn8SBjKfdLcEg,4174
|
350
348
|
dissect/target/tools/mount.py,sha256=8GRYnu4xEmFBHxuIZAYhOMyyTGX8fat1Ou07DNiUnW4,3945
|
351
|
-
dissect/target/tools/query.py,sha256=
|
349
|
+
dissect/target/tools/query.py,sha256=e-yAN9zdQjuOiTuoOQoo17mVEQGGcOgaA9YkF4GYpkM,15394
|
352
350
|
dissect/target/tools/reg.py,sha256=FDsiBBDxjWVUBTRj8xn82vZe-J_d9piM-TKS3PHZCcM,3193
|
353
351
|
dissect/target/tools/shell.py,sha256=dmshIriwdd_UwrdUcTfWkcYD8Z0mjzbDqwyZG-snDdM,50482
|
354
|
-
dissect/target/tools/utils.py,sha256=
|
352
|
+
dissect/target/tools/utils.py,sha256=cGk_DxEqVL0ofxlIC15GD4w3PV5RSE_IaKvVkAxEhR8,11974
|
355
353
|
dissect/target/tools/yara.py,sha256=70k-2VMulf1EdkX03nCACzejaOEcsFHOyX-4E40MdQU,2044
|
356
354
|
dissect/target/tools/dump/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
357
355
|
dissect/target/tools/dump/run.py,sha256=aD84peRS4zHqC78fH7Vd4ni3m1ZmVP70LyMwBRvoDGY,9463
|
@@ -365,10 +363,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z
|
|
365
363
|
dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
|
366
364
|
dissect/target/volumes/md.py,sha256=7ShPtusuLGaIv27SvEETtgsuoQyAa4iAAeOR1NEaajI,1689
|
367
365
|
dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
|
368
|
-
dissect.target-3.20.
|
369
|
-
dissect.target-3.20.
|
370
|
-
dissect.target-3.20.
|
371
|
-
dissect.target-3.20.
|
372
|
-
dissect.target-3.20.
|
373
|
-
dissect.target-3.20.
|
374
|
-
dissect.target-3.20.
|
366
|
+
dissect.target-3.20.dev7.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
|
367
|
+
dissect.target-3.20.dev7.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
|
368
|
+
dissect.target-3.20.dev7.dist-info/METADATA,sha256=ggSv2_Ve2dhbN7iv1CkzxquwwpMxT5pd9xPsfmr46TY,12896
|
369
|
+
dissect.target-3.20.dev7.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
370
|
+
dissect.target-3.20.dev7.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
|
371
|
+
dissect.target-3.20.dev7.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
|
372
|
+
dissect.target-3.20.dev7.dist-info/RECORD,,
|
@@ -1,58 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
import functools
|
4
|
-
from typing import Any, Callable, Optional, Union
|
5
|
-
|
6
|
-
from dissect.util.stream import AlignedStream
|
7
|
-
|
8
|
-
from dissect.target.exceptions import FatalError
|
9
|
-
from dissect.target.loader import Loader
|
10
|
-
|
11
|
-
|
12
|
-
class TargetdStream(AlignedStream):
|
13
|
-
def _read(self, offset: int, length: int) -> bytes:
|
14
|
-
return b""
|
15
|
-
|
16
|
-
|
17
|
-
# Marker interface to indicate this loader loads targets from remote machines
|
18
|
-
class ProxyLoader(Loader):
|
19
|
-
pass
|
20
|
-
|
21
|
-
|
22
|
-
class TargetdInvalidStateError(FatalError):
|
23
|
-
pass
|
24
|
-
|
25
|
-
|
26
|
-
class CommandProxy:
|
27
|
-
def __init__(self, loader: Loader, func: Callable, namespace: Optional[str] = None):
|
28
|
-
self._loader = loader
|
29
|
-
self._func = func
|
30
|
-
self._namespace = namespace or func
|
31
|
-
|
32
|
-
def __getattr__(self, func: Union[Callable, str]) -> CommandProxy:
|
33
|
-
if func == "func":
|
34
|
-
return self._func.func
|
35
|
-
self._func = func
|
36
|
-
return self
|
37
|
-
|
38
|
-
def command(self) -> Callable:
|
39
|
-
namespace = None if self._func == self._namespace else self._namespace
|
40
|
-
return self._get(namespace, self._func)
|
41
|
-
|
42
|
-
def _get(self, namespace: Optional[str], plugin_func: Callable) -> Callable:
|
43
|
-
if namespace:
|
44
|
-
func = functools.update_wrapper(
|
45
|
-
functools.partial(self._loader.plugin_bridge, plugin_func=self._func, namespace=self._namespace),
|
46
|
-
self._loader.plugin_bridge,
|
47
|
-
)
|
48
|
-
else:
|
49
|
-
func = functools.update_wrapper(
|
50
|
-
functools.partial(self._loader.plugin_bridge, plugin_func=plugin_func), self._loader.plugin_bridge
|
51
|
-
)
|
52
|
-
return func
|
53
|
-
|
54
|
-
def __call__(self, *args, **kwargs) -> Any:
|
55
|
-
return self.command()(*args, **kwargs)
|
56
|
-
|
57
|
-
def __repr__(self) -> str:
|
58
|
-
return str(self._get(None, "get")(**{"property_name": self._func}))
|
@@ -1,223 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
import functools
|
4
|
-
import ssl
|
5
|
-
import time
|
6
|
-
import urllib
|
7
|
-
from pathlib import Path
|
8
|
-
from platform import os
|
9
|
-
from typing import Any, Callable, Optional, Union
|
10
|
-
|
11
|
-
from dissect.target.containers.raw import RawContainer
|
12
|
-
from dissect.target.exceptions import LoaderError
|
13
|
-
|
14
|
-
# to avoid loading issues during automatic loader detection
|
15
|
-
from dissect.target.helpers.targetd import (
|
16
|
-
CommandProxy,
|
17
|
-
ProxyLoader,
|
18
|
-
TargetdInvalidStateError,
|
19
|
-
TargetdStream,
|
20
|
-
)
|
21
|
-
from dissect.target.loader import Loader
|
22
|
-
from dissect.target.plugin import Plugin, export
|
23
|
-
from dissect.target.target import Target
|
24
|
-
|
25
|
-
TARGETD_AVAILABLE = False
|
26
|
-
try:
|
27
|
-
from flow import remoting
|
28
|
-
from targetd.clients import Client
|
29
|
-
|
30
|
-
TARGETD_AVAILABLE = True
|
31
|
-
except Exception:
|
32
|
-
pass
|
33
|
-
|
34
|
-
|
35
|
-
class TargetdLoader(ProxyLoader):
|
36
|
-
"""Load remote targets through a broker."""
|
37
|
-
|
38
|
-
instance = None
|
39
|
-
|
40
|
-
def __init__(self, path: Union[Path, str], **kwargs):
|
41
|
-
super().__init__(path)
|
42
|
-
self._plugin_func = None
|
43
|
-
self.client = None
|
44
|
-
self.output = None
|
45
|
-
self.peers = 1
|
46
|
-
self.cacert = None
|
47
|
-
self.adapter = "Flow.Remoting"
|
48
|
-
self.host = "localhost"
|
49
|
-
self.port = 1883
|
50
|
-
self.chunking = True
|
51
|
-
self.local_link = "unix:///tmp/targetd" if os.name == "posix" else "pipe:///tmp/targetd"
|
52
|
-
# @todo Add these options to loader help (when available)
|
53
|
-
self.configurables = [
|
54
|
-
["cacert", Path, "SSL: cacert file"],
|
55
|
-
["host", str, "IP-address of targetd broker"],
|
56
|
-
["port", int, "Port for connecting to targetd broker"],
|
57
|
-
["local_link", str, "Domain socket or named pipe"],
|
58
|
-
["adapter", str, "Adapter to use"],
|
59
|
-
["peers", int, "Minimum number of hosts to wait for before executing query"],
|
60
|
-
["chunking", int, "Chunk mode"],
|
61
|
-
]
|
62
|
-
uri = kwargs.get("parsed_path")
|
63
|
-
if uri is None:
|
64
|
-
raise LoaderError("No URI connection details have been passed.")
|
65
|
-
self.uri = uri.path
|
66
|
-
self.options = dict(urllib.parse.parse_qsl(uri.query, keep_blank_values=True))
|
67
|
-
|
68
|
-
def _process_options(self, target: Target) -> None:
|
69
|
-
for configurable_details in self.configurables:
|
70
|
-
configurable, value_type, description = configurable_details
|
71
|
-
configuration = self.options.get(configurable)
|
72
|
-
if not configuration:
|
73
|
-
default_value = getattr(self, configurable)
|
74
|
-
target.log.warning("%s not configured, using: %s=%s", description, configurable, default_value)
|
75
|
-
else:
|
76
|
-
setattr(self, configurable, value_type(configuration))
|
77
|
-
|
78
|
-
@export(output="record", cache=False)
|
79
|
-
def plugin_bridge(self, *args, **kwargs) -> list[Any]:
|
80
|
-
"""Command Execution Bridge Plugin for Targetd.
|
81
|
-
|
82
|
-
This is a generic plugin interceptor that becomes active only if using
|
83
|
-
the targetd loader. This plugin acts as a bridge to connect to the Targetd broker
|
84
|
-
and will translate the requested plugin-operation into Targetd-commands using the selected
|
85
|
-
adapter (i.e. Flow.Remoting).
|
86
|
-
"""
|
87
|
-
|
88
|
-
if not TARGETD_AVAILABLE:
|
89
|
-
raise ImportError("This loader requires the targetd package to be installed.")
|
90
|
-
|
91
|
-
self.output = None
|
92
|
-
self.has_output = False
|
93
|
-
|
94
|
-
plugin_func = kwargs.pop("plugin_func", None)
|
95
|
-
|
96
|
-
if self.client is None:
|
97
|
-
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
98
|
-
ssl_context.check_hostname = False
|
99
|
-
if self.cacert and not self.cacert.exists():
|
100
|
-
raise LoaderError(f"file not found: {self.cacert}")
|
101
|
-
if self.cacert:
|
102
|
-
ssl_context.load_verify_locations(self.cacert)
|
103
|
-
else:
|
104
|
-
ssl_context.load_default_certs(purpose=ssl.Purpose.SERVER_AUTH)
|
105
|
-
self.client = Client(self.host, self.port, ssl_context, [self.uri], self.local_link, "targetd")
|
106
|
-
self.client.module_fullname = "dissect.target.loaders"
|
107
|
-
self.client.module_fromlist = ["command_runner"]
|
108
|
-
self.client.command = plugin_func
|
109
|
-
try:
|
110
|
-
self.client.start(*args, **kwargs)
|
111
|
-
except Exception:
|
112
|
-
# If something happens that prevents targetd from properly closing/resetting the
|
113
|
-
# connection, this exception is thrown during the next connection and the connection
|
114
|
-
# is closed properly after all so that the next time the loader will be able to
|
115
|
-
# use a new connection with a new session.
|
116
|
-
self.client.close()
|
117
|
-
raise TargetdInvalidStateError("Targetd connection is in invalid state, retry.")
|
118
|
-
else:
|
119
|
-
self.client.command = plugin_func
|
120
|
-
self.client.exec_command(*args, **kwargs)
|
121
|
-
|
122
|
-
while not self.has_output:
|
123
|
-
time.sleep(1)
|
124
|
-
return self.output
|
125
|
-
|
126
|
-
def _get_command(self, func: str, namespace: str = None) -> tuple[Loader, functools.partial]:
|
127
|
-
return (self, CommandProxy(self, func, namespace=namespace))
|
128
|
-
|
129
|
-
def each(self, func: Callable, target: Optional[Target] = None) -> Any:
|
130
|
-
"""Allows you to attach a scriptlet (function) to each of the remote hosts.
|
131
|
-
The results of the function are accumulated using +=. Exceptions are
|
132
|
-
catched and logged as warnings.
|
133
|
-
|
134
|
-
Usage:
|
135
|
-
|
136
|
-
def my_function(remote_target):
|
137
|
-
...do something interesting...
|
138
|
-
|
139
|
-
targets = Target.open( targetd://... )
|
140
|
-
targets.each(my_function)
|
141
|
-
|
142
|
-
"""
|
143
|
-
result = None
|
144
|
-
if not self.client:
|
145
|
-
target.init()
|
146
|
-
for peer in self.client.peers:
|
147
|
-
target.select(peer)
|
148
|
-
try:
|
149
|
-
if result is None:
|
150
|
-
result = func(target)
|
151
|
-
else:
|
152
|
-
result += func(target)
|
153
|
-
except Exception as failure:
|
154
|
-
target.log.warning("Exception while applying function to target: %s: %s", peer, failure)
|
155
|
-
return result
|
156
|
-
|
157
|
-
def _add_plugin(self, plugin: Plugin):
|
158
|
-
plugin.check_compatibe = lambda: True
|
159
|
-
|
160
|
-
def map(self, target: Target) -> None:
|
161
|
-
if TargetdLoader.instance:
|
162
|
-
raise Exception("You can only initiated 1 targetd control connection per session.")
|
163
|
-
self._process_options(target)
|
164
|
-
target.disks.add(RawContainer(TargetdStream()))
|
165
|
-
target.get_function = self._get_command
|
166
|
-
target.add_plugin = self._add_plugin
|
167
|
-
target.each = functools.update_wrapper(functools.partial(self.each, target=target), self.each)
|
168
|
-
TargetdLoader.instance = self
|
169
|
-
|
170
|
-
@staticmethod
|
171
|
-
def detect(path: Path) -> bool:
|
172
|
-
# You can only activate this loader by URI-scheme "targetd://"
|
173
|
-
return False
|
174
|
-
|
175
|
-
def __del__(self) -> None:
|
176
|
-
if self.client:
|
177
|
-
self.client.close()
|
178
|
-
|
179
|
-
|
180
|
-
if TARGETD_AVAILABLE:
|
181
|
-
# Loader has to provide the control script for targetd in this case
|
182
|
-
def command_runner(link: str, targetd: Client, *args, **kwargs) -> None:
|
183
|
-
caller = TargetdLoader.instance
|
184
|
-
if not targetd.rpcs:
|
185
|
-
targetd.easy_connect_remoting(remoting, link, caller.peers)
|
186
|
-
|
187
|
-
obj = targetd.rpcs
|
188
|
-
if namespace := kwargs.get("namespace", None):
|
189
|
-
obj = getattr(obj, namespace)
|
190
|
-
|
191
|
-
caller.has_output = True
|
192
|
-
if targetd.command == "init":
|
193
|
-
caller.output = True
|
194
|
-
return
|
195
|
-
|
196
|
-
if targetd.command == "select":
|
197
|
-
targetd.rpcs.select(*args)
|
198
|
-
caller.output = True
|
199
|
-
return
|
200
|
-
|
201
|
-
if targetd.command == "deselect":
|
202
|
-
targetd.rpcs.deselect()
|
203
|
-
caller.output = True
|
204
|
-
return
|
205
|
-
|
206
|
-
func = getattr(obj, targetd.command)
|
207
|
-
|
208
|
-
result = func(*args, **kwargs)
|
209
|
-
if result is not None:
|
210
|
-
if targetd.command == "get":
|
211
|
-
result = list(result)[0]
|
212
|
-
elif caller.chunking:
|
213
|
-
data = []
|
214
|
-
gen = func()
|
215
|
-
targetd.reset()
|
216
|
-
while True:
|
217
|
-
try:
|
218
|
-
data.append(next(gen))
|
219
|
-
except Exception:
|
220
|
-
break
|
221
|
-
result = data
|
222
|
-
caller.output = result
|
223
|
-
targetd.reset()
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|