dissect.target 3.20.dev43__py3-none-any.whl → 3.20.dev44__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/loader.py CHANGED
@@ -205,4 +205,5 @@ register("velociraptor", "VelociraptorLoader")
205
205
  register("smb", "SmbLoader")
206
206
  register("cb", "CbLoader")
207
207
  register("cyber", "CyberLoader")
208
+ register("proxmox", "ProxmoxLoader")
208
209
  register("multiraw", "MultiRawLoader") # Should be last
@@ -0,0 +1,68 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from pathlib import Path
5
+
6
+ from dissect.target import container
7
+ from dissect.target.loader import Loader
8
+ from dissect.target.target import Target
9
+
10
+ RE_VOLUME_ID = re.compile(r"(?:file=)?([^:]+):([^,]+)")
11
+
12
+
13
+ class ProxmoxLoader(Loader):
14
+ """Loader for Proxmox VM configuration files.
15
+
16
+ Proxmox uses volume identifiers in the format of ``storage_id:volume_id``. The ``storage_id`` maps to a
17
+ storage configuration in ``/etc/pve/storage.cfg``. The ``volume_id`` is the name of the volume within
18
+ that configuration.
19
+
20
+ This loader currently does not support parsing the storage configuration, so it will attempt to open the
21
+ volume directly from the same directory as the configuration file, or from ``/dev/pve/`` (default LVM config).
22
+ If the volume is not found, it will log a warning.
23
+ """
24
+
25
+ def __init__(self, path: Path, **kwargs):
26
+ path = path.resolve()
27
+ super().__init__(path)
28
+ self.base_dir = path.parent
29
+
30
+ @staticmethod
31
+ def detect(path: Path) -> bool:
32
+ if path.suffix.lower() != ".conf":
33
+ return False
34
+
35
+ with path.open("rb") as fh:
36
+ lines = fh.read(512).split(b"\n")
37
+ needles = [b"cpu:", b"memory:", b"name:"]
38
+ return all(any(needle in line for line in lines) for needle in needles)
39
+
40
+ def map(self, target: Target) -> None:
41
+ with self.path.open("rt") as fh:
42
+ for line in fh:
43
+ if not (line := line.strip()):
44
+ continue
45
+
46
+ key, value = line.split(":", 1)
47
+ value = value.strip()
48
+
49
+ if key.startswith(("scsi", "sata", "ide", "virtio")) and key[-1].isdigit():
50
+ # https://pve.proxmox.com/wiki/Storage
51
+ if match := RE_VOLUME_ID.match(value):
52
+ storage_id, volume_id = match.groups()
53
+
54
+ # TODO: parse the storage information from /etc/pve/storage.cfg
55
+ # For now, let's try a few assumptions
56
+ disk_path = None
57
+ if (path := self.base_dir.joinpath(volume_id)).exists():
58
+ disk_path = path
59
+ elif (path := self.base_dir.joinpath("/dev/pve/").joinpath(volume_id)).exists():
60
+ disk_path = path
61
+
62
+ if disk_path:
63
+ try:
64
+ target.disks.add(container.open(disk_path))
65
+ except Exception:
66
+ target.log.exception("Failed to open disk: %s", disk_path)
67
+ else:
68
+ target.log.warning("Unable to find disk: %s:%s", storage_id, volume_id)
dissect/target/plugin.py CHANGED
@@ -57,17 +57,18 @@ log = logging.getLogger(__name__)
57
57
 
58
58
 
59
59
  class OperatingSystem(StrEnum):
60
- LINUX = "linux"
61
- WINDOWS = "windows"
62
- ESXI = "esxi"
60
+ ANDROID = "android"
63
61
  BSD = "bsd"
62
+ CITRIX = "citrix-netscaler"
63
+ ESXI = "esxi"
64
+ FORTIOS = "fortios"
65
+ IOS = "ios"
66
+ LINUX = "linux"
64
67
  OSX = "osx"
68
+ PROXMOX = "proxmox"
65
69
  UNIX = "unix"
66
- ANDROID = "android"
67
70
  VYOS = "vyos"
68
- IOS = "ios"
69
- FORTIOS = "fortios"
70
- CITRIX = "citrix-netscaler"
71
+ WINDOWS = "windows"
71
72
 
72
73
 
73
74
  def export(*args, **kwargs) -> Callable:
@@ -0,0 +1,23 @@
1
+ from typing import Iterator
2
+
3
+ from dissect.target.exceptions import UnsupportedPluginError
4
+ from dissect.target.helpers.record import ChildTargetRecord
5
+ from dissect.target.plugin import ChildTargetPlugin
6
+
7
+
8
+ class ProxmoxChildTargetPlugin(ChildTargetPlugin):
9
+ """Child target plugin that yields from the VM listing."""
10
+
11
+ __type__ = "proxmox"
12
+
13
+ def check_compatible(self) -> None:
14
+ if self.target.os != "proxmox":
15
+ raise UnsupportedPluginError("Not a Proxmox operating system")
16
+
17
+ def list_children(self) -> Iterator[ChildTargetRecord]:
18
+ for vm in self.target.vmlist():
19
+ yield ChildTargetRecord(
20
+ type=self.__type__,
21
+ path=vm.path,
22
+ _target=self.target,
23
+ )
@@ -23,6 +23,7 @@ class UnixPlugin(OSPlugin):
23
23
  def __init__(self, target: Target):
24
24
  super().__init__(target)
25
25
  self._add_mounts()
26
+ self._add_devices()
26
27
  self._hostname_dict = self._parse_hostname_string()
27
28
  self._hosts_dict = self._parse_hosts_string()
28
29
  self._os_release = self._parse_os_release()
@@ -247,6 +248,17 @@ class UnixPlugin(OSPlugin):
247
248
  self.target.log.debug("Mounting %s (%s) at %s", fs, fs.volume, mount_point)
248
249
  self.target.fs.mount(mount_point, fs)
249
250
 
251
+ def _add_devices(self) -> None:
252
+ """Add some virtual block devices to the target.
253
+
254
+ Currently only adds LVM devices.
255
+ """
256
+ vfs = self.target.fs.append_layer()
257
+
258
+ for volume in self.target.volumes:
259
+ if volume.vs and volume.vs.__type__ == "lvm":
260
+ vfs.map_file_fh(f"/dev/{volume.raw.vg.name}/{volume.raw.name}", volume)
261
+
250
262
  def _parse_os_release(self, glob: str | None = None) -> dict[str, str]:
251
263
  """Parse files containing Unix version information.
252
264
 
@@ -0,0 +1,141 @@
1
+ from __future__ import annotations
2
+
3
+ import stat
4
+ from io import BytesIO
5
+ from typing import BinaryIO
6
+
7
+ from dissect.sql import sqlite3
8
+ from dissect.util.stream import BufferedStream
9
+
10
+ from dissect.target.filesystem import (
11
+ Filesystem,
12
+ VirtualDirectory,
13
+ VirtualFile,
14
+ VirtualFilesystem,
15
+ )
16
+ from dissect.target.helpers import fsutil
17
+ from dissect.target.plugins.os.unix._os import OperatingSystem, export
18
+ from dissect.target.plugins.os.unix.linux.debian._os import DebianPlugin
19
+ from dissect.target.target import Target
20
+
21
+
22
+ class ProxmoxPlugin(DebianPlugin):
23
+ @classmethod
24
+ def detect(cls, target: Target) -> Filesystem | None:
25
+ for fs in target.filesystems:
26
+ if fs.exists("/etc/pve") or fs.exists("/var/lib/pve"):
27
+ return fs
28
+
29
+ return None
30
+
31
+ @classmethod
32
+ def create(cls, target: Target, sysvol: Filesystem) -> ProxmoxPlugin:
33
+ obj = super().create(target, sysvol)
34
+
35
+ if (config_db := target.fs.path("/var/lib/pve-cluster/config.db")).exists():
36
+ with config_db.open("rb") as fh:
37
+ vfs = _create_pmxcfs(fh, obj.hostname)
38
+
39
+ target.fs.mount("/etc/pve", vfs)
40
+
41
+ return obj
42
+
43
+ @export(property=True)
44
+ def version(self) -> str:
45
+ """Returns Proxmox VE version with underlying OS release."""
46
+
47
+ for pkg in self.target.dpkg.status():
48
+ if pkg.name == "proxmox-ve":
49
+ distro_name = self._os_release.get("PRETTY_NAME", "")
50
+ return f"{pkg.name} {pkg.version} ({distro_name})"
51
+
52
+ @export(property=True)
53
+ def os(self) -> str:
54
+ return OperatingSystem.PROXMOX.value
55
+
56
+
57
+ DT_DIR = 4
58
+ DT_REG = 8
59
+
60
+
61
+ def _create_pmxcfs(fh: BinaryIO, hostname: str | None = None) -> VirtualFilesystem:
62
+ # https://pve.proxmox.com/wiki/Proxmox_Cluster_File_System_(pmxcfs)
63
+ db = sqlite3.SQLite3(fh)
64
+
65
+ entries = {row.inode: row for row in db.table("tree")}
66
+
67
+ vfs = VirtualFilesystem()
68
+ for entry in entries.values():
69
+ if entry.type == DT_DIR:
70
+ cls = ProxmoxConfigDirectoryEntry
71
+ elif entry.type == DT_REG:
72
+ cls = ProxmoxConfigFileEntry
73
+ else:
74
+ raise ValueError(f"Unknown pmxcfs file type: {entry.type}")
75
+
76
+ parts = []
77
+ current = entry
78
+ while current.parent != 0:
79
+ parts.append(current.name)
80
+ current = entries[current.parent]
81
+ parts.append(current.name)
82
+
83
+ path = "/".join(parts[::-1])
84
+ vfs.map_file_entry(path, cls(vfs, path, entry))
85
+
86
+ if hostname:
87
+ node_root = vfs.path(f"nodes/{hostname}")
88
+ vfs.symlink(str(node_root), "local")
89
+ vfs.symlink(str(node_root / "lxc"), "lxc")
90
+ vfs.symlink(str(node_root / "openvz"), "openvz")
91
+ vfs.symlink(str(node_root / "qemu-server"), "qemu-server")
92
+
93
+ # TODO: .version, .members, .vmlist, maybe .clusterlog and .rrd?
94
+
95
+ return vfs
96
+
97
+
98
+ class ProxmoxConfigFileEntry(VirtualFile):
99
+ def open(self) -> BinaryIO:
100
+ return BufferedStream(BytesIO(self.entry.data or b""))
101
+
102
+ def lstat(self) -> fsutil.stat_result:
103
+ # ['mode', 'addr', 'dev', 'nlink', 'uid', 'gid', 'size', 'atime', 'mtime', 'ctime']
104
+ return fsutil.stat_result(
105
+ [
106
+ stat.S_IFREG | 0o640,
107
+ self.entry.inode,
108
+ id(self.fs),
109
+ 1,
110
+ 0,
111
+ 0,
112
+ len(self.entry.data) if self.entry.data else 0,
113
+ 0,
114
+ self.entry.mtime,
115
+ 0,
116
+ ]
117
+ )
118
+
119
+
120
+ class ProxmoxConfigDirectoryEntry(VirtualDirectory):
121
+ def __init__(self, fs: VirtualFilesystem, path: str, entry: sqlite3.Row):
122
+ super().__init__(fs, path)
123
+ self.entry = entry
124
+
125
+ def lstat(self) -> fsutil.stat_result:
126
+ """Return the stat information of the given path, without resolving links."""
127
+ # ['mode', 'addr', 'dev', 'nlink', 'uid', 'gid', 'size', 'atime', 'mtime', 'ctime']
128
+ return fsutil.stat_result(
129
+ [
130
+ stat.S_IFDIR | 0o755,
131
+ self.entry.inode,
132
+ id(self.fs),
133
+ 1,
134
+ 0,
135
+ 0,
136
+ 0,
137
+ 0,
138
+ self.entry.mtime,
139
+ 0,
140
+ ]
141
+ )
@@ -0,0 +1,29 @@
1
+ from typing import Iterator
2
+
3
+ from dissect.target.exceptions import UnsupportedPluginError
4
+ from dissect.target.helpers.record import TargetRecordDescriptor
5
+ from dissect.target.plugin import Plugin, export
6
+
7
+ VirtualMachineRecord = TargetRecordDescriptor(
8
+ "proxmox/vm",
9
+ [
10
+ ("string", "path"),
11
+ ],
12
+ )
13
+
14
+
15
+ class VirtualMachinePlugin(Plugin):
16
+ """Plugin to list Proxmox virtual machines."""
17
+
18
+ def check_compatible(self) -> None:
19
+ if self.target.os != "proxmox":
20
+ raise UnsupportedPluginError("Not a Proxmox operating system")
21
+
22
+ @export(record=VirtualMachineRecord)
23
+ def vmlist(self) -> Iterator[VirtualMachineRecord]:
24
+ """List Proxmox virtual machines on this node."""
25
+ for config in self.target.fs.path("/etc/pve/qemu-server").iterdir():
26
+ yield VirtualMachineRecord(
27
+ path=config,
28
+ _target=self.target,
29
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.target
3
- Version: 3.20.dev43
3
+ Version: 3.20.dev44
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,8 +2,8 @@ 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=11B3W9IpfdTU9GJiKONGffuGeICog7J9ypXZ6mffsBE,7337
6
- dissect/target/plugin.py,sha256=bXWfFOX9B4p1u7T7crASf0jjPrSp8X0wu_jHze2-t_g,50658
5
+ dissect/target/loader.py,sha256=ZlCI7ZyPpysuSKndOiRz_rrGb30_jLMdFD6qObY0Vzg,7374
6
+ dissect/target/plugin.py,sha256=iUc7OmQJ0wwYJeR7L4VBNJ0AjgYWV69FN0NHgWYaLYI,50682
7
7
  dissect/target/report.py,sha256=06uiP4MbNI8cWMVrC1SasNS-Yg6ptjVjckwj8Yhe0Js,7958
8
8
  dissect/target/target.py,sha256=-UoFO_fqS6XPf77RDHSV4gNRi95wwwpgWq7zIhNVUGk,32719
9
9
  dissect/target/volume.py,sha256=aQZAJiny8jjwkc9UtwIRwy7nINXjCxwpO-_UDfh6-BA,15801
@@ -93,6 +93,7 @@ dissect/target/loaders/overlay.py,sha256=tj99HKvNG5_JbGfb1WCv4KNSbXXSnEcPQY5XT-J
93
93
  dissect/target/loaders/ovf.py,sha256=ELMq6J2y6cPKbp7pjWAqMMnFYefWxXNqzIiAQdvGGXQ,1061
94
94
  dissect/target/loaders/phobos.py,sha256=XtxF7FZXfZrXJruFUZUQzxlREyfc86dTxph7BNoNMvw,2277
95
95
  dissect/target/loaders/profile.py,sha256=5ylgmzEEGyBFW3izvb-BZ7dGByXN9OFyRnnggR98P9w,1667
96
+ dissect/target/loaders/proxmox.py,sha256=HP7yCrXze1DmMud7730OPMIwUAEaeoXK0YO6i2XSunM,2778
96
97
  dissect/target/loaders/pvm.py,sha256=b-PvHNTbRVdOnf7-OR5dbikbDTCFlW85b-9Z8PEL2Cs,406
97
98
  dissect/target/loaders/pvs.py,sha256=dMqdYSBQtH9QLM3tdu0mokLBcn73edg_HUtYtqrdi6E,955
98
99
  dissect/target/loaders/raw.py,sha256=tleNWoO0BkC32ExBIPVOpzrQHXXHChZXoZr02oYuC8A,674
@@ -163,6 +164,7 @@ dissect/target/plugins/child/docker.py,sha256=frBZ8UUzbtkT9VrK1fwUzXDAdkHESdPCb-
163
164
  dissect/target/plugins/child/esxi.py,sha256=EHpBRv5dizvJVIhtVR7frkUnI9GR7lrbSWPGoOpKhRw,753
164
165
  dissect/target/plugins/child/hyperv.py,sha256=R2qVeu4p_9V53jO-65znN0LwX9v3FVA-9jbbtOQcEz8,2236
165
166
  dissect/target/plugins/child/parallels.py,sha256=jeBT_NvTQbQBaUjqGWTy2I5Q5OWlrogoyWHRXjOhLis,2255
167
+ dissect/target/plugins/child/proxmox.py,sha256=qjeact60fpUXsOciAIJ776fpcnkO6hTZjeUyx92I2HQ,755
166
168
  dissect/target/plugins/child/qemu.py,sha256=vNzQwzFO964jYaI67MlX8vpWyHxpegjIU5F29zHKOGI,791
167
169
  dissect/target/plugins/child/virtuozzo.py,sha256=raXaDTyGPY5JuNgyUOn_qzHaPa0sokG3nGZ5_7eDPyA,1303
168
170
  dissect/target/plugins/child/vmware_workstation.py,sha256=eLvgi_aFUaMN1tC5X4RN85nKQdsuRQygrFYtNMAERTc,2030
@@ -194,7 +196,7 @@ dissect/target/plugins/general/scrape.py,sha256=Fz7BNXflvuxlnVulyyDhLpyU8D_hJdH6
194
196
  dissect/target/plugins/general/users.py,sha256=yy9gvRXfN9BT71v4Xqo5hpwfgN9he9Otu8TBPZ_Tegs,3009
195
197
  dissect/target/plugins/os/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
196
198
  dissect/target/plugins/os/unix/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
197
- dissect/target/plugins/os/unix/_os.py,sha256=I-waC7Hn9rMJRI2JUDreLnU_LS54Zj3hix7BfJLV-xg,14651
199
+ dissect/target/plugins/os/unix/_os.py,sha256=prvcuIkJ1jmbb_g1CtgHmmJUcGjFaZn3yU6VPNMJ27k,15061
198
200
  dissect/target/plugins/os/unix/applications.py,sha256=AUgZRP35FzswGyFyChj2o4dfGO34Amc6nqHgiMEaqdI,3129
199
201
  dissect/target/plugins/os/unix/cronjobs.py,sha256=tgWQ3BUZpfyvRzodMwGtwFUdPjZ17k7ZRbZ9Q8wmXPk,3393
200
202
  dissect/target/plugins/os/unix/datetime.py,sha256=gKfBdPyUirt3qmVYfOJ1oZXRPn8wRzssbZxR_ARrtk8,1518
@@ -242,6 +244,9 @@ dissect/target/plugins/os/unix/linux/debian/_os.py,sha256=GI19ZqcyfZ1mUYg2NCx93H
242
244
  dissect/target/plugins/os/unix/linux/debian/apt.py,sha256=uKfW77pbgxYSe9g6hpih19-xfgvCB954Ygx21j-ydzk,4388
243
245
  dissect/target/plugins/os/unix/linux/debian/dpkg.py,sha256=6A3mb77NREdDWDTFrmcfCF_XxHMmD4_5eHqHEl_7Vqg,5816
244
246
  dissect/target/plugins/os/unix/linux/debian/snap.py,sha256=YVz1N54eQsj2_22LUJIj6Gg-huwT4xDEcOAZn9Tvgw4,3023
247
+ dissect/target/plugins/os/unix/linux/debian/proxmox/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
248
+ dissect/target/plugins/os/unix/linux/debian/proxmox/_os.py,sha256=OrQsPcz3fERVnSJKRMXwWQCoVN6CFuC-pJIVHtU-g9M,4257
249
+ dissect/target/plugins/os/unix/linux/debian/proxmox/vm.py,sha256=Z5IDA5Oot4P2hh65n5cgUMUi_TMIgSexIO2ngOgxPo0,911
245
250
  dissect/target/plugins/os/unix/linux/debian/vyos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
246
251
  dissect/target/plugins/os/unix/linux/debian/vyos/_os.py,sha256=TPjcfv1n68RCe3Er4aCVQwQDCZwJT-NLvje3kPjDfhk,1744
247
252
  dissect/target/plugins/os/unix/linux/fortios/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -373,10 +378,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z
373
378
  dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
374
379
  dissect/target/volumes/md.py,sha256=7ShPtusuLGaIv27SvEETtgsuoQyAa4iAAeOR1NEaajI,1689
375
380
  dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
376
- dissect.target-3.20.dev43.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
377
- dissect.target-3.20.dev43.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
378
- dissect.target-3.20.dev43.dist-info/METADATA,sha256=FO2hxtOkklCzr7ZrCmD1SeMtL5Dqy8jufOSQFRUQVRY,12897
379
- dissect.target-3.20.dev43.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
380
- dissect.target-3.20.dev43.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
381
- dissect.target-3.20.dev43.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
382
- dissect.target-3.20.dev43.dist-info/RECORD,,
381
+ dissect.target-3.20.dev44.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
382
+ dissect.target-3.20.dev44.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
383
+ dissect.target-3.20.dev44.dist-info/METADATA,sha256=Sgsho9JR5_66txpbY0u-KInNUTMmgflhCY23idL5Iuo,12897
384
+ dissect.target-3.20.dev44.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
385
+ dissect.target-3.20.dev44.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
386
+ dissect.target-3.20.dev44.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
387
+ dissect.target-3.20.dev44.dist-info/RECORD,,