|
7 | 7 | """ |
8 | 8 |
|
9 | 9 | import urllib.parse |
| 10 | +from dataclasses import asdict, dataclass, field |
10 | 11 | from enum import Enum |
11 | 12 | from typing import Any, Optional, Union |
12 | 13 | from uuid import UUID |
|
20 | 21 | ID = Union[str, UUID] |
21 | 22 |
|
22 | 23 |
|
| 24 | +@dataclass |
| 25 | +class PortRange: |
| 26 | + """ |
| 27 | + Represents a range of ports. |
| 28 | +
|
| 29 | + Attributes: |
| 30 | + start: The starting port number. |
| 31 | + end: The ending port number. |
| 32 | + """ |
| 33 | + |
| 34 | + start: int |
| 35 | + end: int |
| 36 | + |
| 37 | + |
| 38 | +@dataclass |
| 39 | +class Port: |
| 40 | + """ |
| 41 | + Represents a port configuration for scanning. |
| 42 | +
|
| 43 | + Attributes: |
| 44 | + protocol: The protocol to use ("tcp" or "udp"). |
| 45 | + range: A list of port ranges to scan. |
| 46 | + """ |
| 47 | + |
| 48 | + protocol: str # e.g., "tcp", "udp" |
| 49 | + range: list[PortRange] |
| 50 | + |
| 51 | + |
| 52 | +@dataclass |
| 53 | +class CredentialUP: |
| 54 | + """ |
| 55 | + Represents username/password credentials for a service. |
| 56 | +
|
| 57 | + Attributes: |
| 58 | + username: The login username. |
| 59 | + password: The login password. |
| 60 | + privilege_username: Optional privilege escalation username. |
| 61 | + privilege_password: Optional privilege escalation password. |
| 62 | + """ |
| 63 | + |
| 64 | + username: str |
| 65 | + password: str |
| 66 | + privilege_username: Optional[str] = None |
| 67 | + privilege_password: Optional[str] = None |
| 68 | + |
| 69 | + |
| 70 | +@dataclass |
| 71 | +class CredentialKRB5: |
| 72 | + """ |
| 73 | + Represents Kerberos credentials. |
| 74 | +
|
| 75 | + Attributes: |
| 76 | + username: Kerberos username. |
| 77 | + password: Kerberos password. |
| 78 | + realm: Kerberos realm. |
| 79 | + kdc: Key Distribution Center hostname. |
| 80 | + """ |
| 81 | + |
| 82 | + username: str |
| 83 | + password: str |
| 84 | + realm: str |
| 85 | + kdc: str |
| 86 | + |
| 87 | + |
| 88 | +@dataclass |
| 89 | +class CredentialUSK: |
| 90 | + """ |
| 91 | + Represents credentials using a user/SSH key combination. |
| 92 | +
|
| 93 | + Attributes: |
| 94 | + username: SSH username. |
| 95 | + password: Password for SSH key (if encrypted). |
| 96 | + private: Private key content or reference. |
| 97 | + privilege_username: Optional privilege escalation username. |
| 98 | + privilege_password: Optional privilege escalation password. |
| 99 | + """ |
| 100 | + |
| 101 | + username: str |
| 102 | + password: str |
| 103 | + private: str |
| 104 | + privilege_username: Optional[str] = None |
| 105 | + privilege_password: Optional[str] = None |
| 106 | + |
| 107 | + |
| 108 | +@dataclass |
| 109 | +class CredentialSNMP: |
| 110 | + """ |
| 111 | + Represents SNMP credentials. |
| 112 | +
|
| 113 | + Attributes: |
| 114 | + username: SNMP username. |
| 115 | + password: SNMP authentication password. |
| 116 | + community: SNMP community string. |
| 117 | + auth_algorithm: Authentication algorithm (e.g., "md5"). |
| 118 | + privacy_password: Privacy password for SNMPv3. |
| 119 | + privacy_algorithm: Privacy algorithm (e.g., "aes"). |
| 120 | + """ |
| 121 | + |
| 122 | + username: str |
| 123 | + password: str |
| 124 | + community: str |
| 125 | + auth_algorithm: str |
| 126 | + privacy_password: str |
| 127 | + privacy_algorithm: str |
| 128 | + |
| 129 | + |
| 130 | +@dataclass |
| 131 | +class Credential: |
| 132 | + """ |
| 133 | + Represents a full credential configuration for a specific service. |
| 134 | +
|
| 135 | + Attributes: |
| 136 | + service: Name of the service (e.g., "ssh", "snmp"). |
| 137 | + port: Port number associated with the service. |
| 138 | + up: Optional username/password credentials. |
| 139 | + krb5: Optional Kerberos credentials. |
| 140 | + usk: Optional user/SSH key credentials. |
| 141 | + snmp: Optional SNMP credentials. |
| 142 | + """ |
| 143 | + |
| 144 | + service: str |
| 145 | + port: int |
| 146 | + up: Optional[CredentialUP] = None |
| 147 | + krb5: Optional[CredentialKRB5] = None |
| 148 | + usk: Optional[CredentialUSK] = None |
| 149 | + snmp: Optional[CredentialSNMP] = None |
| 150 | + |
| 151 | + |
| 152 | +@dataclass |
| 153 | +class Target: |
| 154 | + """ |
| 155 | + Represents the scan target configuration. |
| 156 | +
|
| 157 | + Attributes: |
| 158 | + hosts: List of target IPs or hostnames. |
| 159 | + excluded_hosts: List of IPs or hostnames to exclude. |
| 160 | + ports: List of port configurations. |
| 161 | + credentials: List of credentials to use during scanning. |
| 162 | + alive_test_ports: Port ranges used to test if hosts are alive. |
| 163 | + alive_test_methods: Methods used to check if hosts are alive (e.g., "icmp"). |
| 164 | + reverse_lookup_unify: Whether to unify reverse lookup results. |
| 165 | + reverse_lookup_only: Whether to rely solely on reverse DNS lookups. |
| 166 | + """ |
| 167 | + |
| 168 | + hosts: list[str] |
| 169 | + excluded_hosts: list[str] = field(default_factory=list) |
| 170 | + ports: list[Port] = field(default_factory=list) |
| 171 | + credentials: list[Credential] = field(default_factory=list) |
| 172 | + alive_test_ports: list[Port] = field(default_factory=list) |
| 173 | + alive_test_methods: list[str] = field(default_factory=list) |
| 174 | + reverse_lookup_unify: bool = False |
| 175 | + reverse_lookup_only: bool = False |
| 176 | + |
| 177 | + |
| 178 | +@dataclass |
| 179 | +class VTParameter: |
| 180 | + """ |
| 181 | + Represents a parameter for a specific vulnerability test. |
| 182 | +
|
| 183 | + Attributes: |
| 184 | + id: Identifier of the VT parameter. |
| 185 | + value: Value to assign to the parameter. |
| 186 | + """ |
| 187 | + |
| 188 | + id: int |
| 189 | + value: str |
| 190 | + |
| 191 | + |
| 192 | +@dataclass |
| 193 | +class VTSelection: |
| 194 | + """ |
| 195 | + Represents a selected vulnerability test (VT) and its parameters. |
| 196 | +
|
| 197 | + Attributes: |
| 198 | + oid: The OID (Object Identifier) of the VT. |
| 199 | + parameters: A list of parameters to customize VT behavior. |
| 200 | + """ |
| 201 | + |
| 202 | + oid: str |
| 203 | + parameters: list[VTParameter] = field(default_factory=list) |
| 204 | + |
| 205 | + |
| 206 | +@dataclass |
| 207 | +class ScanPreference: |
| 208 | + """ |
| 209 | + Represents a scan-level preference or configuration option. |
| 210 | +
|
| 211 | + Attributes: |
| 212 | + id: Preference ID or name (e.g., "max_checks", "scan_speed"). |
| 213 | + value: The value assigned to the preference. |
| 214 | + """ |
| 215 | + |
| 216 | + id: str |
| 217 | + value: str |
| 218 | + |
| 219 | + |
| 220 | +def _to_dict(obj: Any) -> Any: |
| 221 | + """Recursively convert dataclass instances to dictionaries.""" |
| 222 | + if isinstance(obj, list): |
| 223 | + return [_to_dict(item) for item in obj] |
| 224 | + elif hasattr(obj, "__dataclass_fields__"): |
| 225 | + return {k: _to_dict(v) for k, v in asdict(obj).items() if v is not None} |
| 226 | + return obj |
| 227 | + |
| 228 | + |
23 | 229 | class ScanAction(str, Enum): |
24 | 230 | """ |
25 | 231 | Enumeration of valid scan actions supported by the openvasd API. |
@@ -47,31 +253,34 @@ class ScansAPI(OpenvasdAPI): |
47 | 253 |
|
48 | 254 | def create( |
49 | 255 | self, |
50 | | - target: dict[str, Any], |
51 | | - vt_selection: list[dict[str, Any]], |
| 256 | + target: Target, |
| 257 | + vt_selection: list[VTSelection], |
52 | 258 | *, |
53 | | - scanner_params: Optional[dict[str, Any]] = None, |
| 259 | + scan_preferences: Optional[list[ScanPreference]] = None, |
54 | 260 | ) -> httpx.Response: |
55 | 261 | """ |
56 | | - Create a new scan with the specified target and VT selection. |
| 262 | + Create a new scan with the specified target configuration and VT selection. |
57 | 263 |
|
58 | 264 | Args: |
59 | | - target: Dictionary describing the scan target (e.g., host and port). |
60 | | - vt_selection: List of dictionaries specifying which VTs to run. |
61 | | - scanner_params: Optional dictionary of scan preferences. |
| 265 | + target: A `Target` dataclass instance describing the scan target(s), including hosts, ports, credentials, and alive test settings. |
| 266 | + vt_selection: A list of `VTSelection` instances specifying which vulnerability tests (VTs) to include in the scan. |
| 267 | + scan_preferences: Optional list of `ScanPreference` instances to customize scan behavior (e.g., number of threads, timeout values). |
62 | 268 |
|
63 | 269 | Returns: |
64 | | - The full HTTP response of the POST /scans request. |
| 270 | + The full HTTP response returned by the POST /scans request. |
65 | 271 |
|
66 | 272 | Raises: |
67 | 273 | httpx.HTTPStatusError: If the server responds with an error status |
68 | 274 | and the exception is not suppressed. |
69 | 275 |
|
70 | 276 | See: POST /scans in the openvasd API documentation. |
71 | 277 | """ |
72 | | - request_json = {"target": target, "vts": vt_selection} |
73 | | - if scanner_params: |
74 | | - request_json["scan_preferences"] = scanner_params |
| 278 | + request_json = { |
| 279 | + "target": _to_dict(target), |
| 280 | + "vts": _to_dict(vt_selection), |
| 281 | + } |
| 282 | + if scan_preferences: |
| 283 | + request_json["scan_preferences"] = _to_dict(scan_preferences) |
75 | 284 |
|
76 | 285 | try: |
77 | 286 | response = self._client.post("/scans", json=request_json) |
@@ -329,3 +538,25 @@ def stop(self, scan_id: ID) -> int: |
329 | 538 | See: POST /scans/{id} with action=stop in the openvasd API documentation. |
330 | 539 | """ |
331 | 540 | return self._run_action(scan_id, ScanAction.STOP) |
| 541 | + |
| 542 | + def get_preferences(self) -> httpx.Response: |
| 543 | + """ |
| 544 | + Retrieve all available scan preferences from the scanner. |
| 545 | +
|
| 546 | + Returns: |
| 547 | + The full HTTP response of the GET /scans/preferences request. |
| 548 | +
|
| 549 | + Raises: |
| 550 | + httpx.HTTPStatusError: If the server responds with an error status |
| 551 | + and the exception is not suppressed. |
| 552 | +
|
| 553 | + See: GET /scans/preferences in the openvasd API documentation. |
| 554 | + """ |
| 555 | + try: |
| 556 | + response = self._client.get("/scans/preferences") |
| 557 | + response.raise_for_status() |
| 558 | + return response |
| 559 | + except httpx.HTTPStatusError as e: |
| 560 | + if self._suppress_exceptions: |
| 561 | + return e.response |
| 562 | + raise |
0 commit comments