Skip to content

Commit 7102566

Browse files
ozgengreenbonebot
authored andcommitted
Add dataclass-based scan creation and get_preferences endpoint
1 parent ea59759 commit 7102566

File tree

2 files changed

+417
-29
lines changed

2 files changed

+417
-29
lines changed

gvm/protocols/http/openvasd/_scans.py

Lines changed: 242 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"""
88

99
import urllib.parse
10+
from dataclasses import asdict, dataclass, field
1011
from enum import Enum
1112
from typing import Any, Optional, Union
1213
from uuid import UUID
@@ -20,6 +21,211 @@
2021
ID = Union[str, UUID]
2122

2223

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+
23229
class ScanAction(str, Enum):
24230
"""
25231
Enumeration of valid scan actions supported by the openvasd API.
@@ -47,31 +253,34 @@ class ScansAPI(OpenvasdAPI):
47253

48254
def create(
49255
self,
50-
target: dict[str, Any],
51-
vt_selection: list[dict[str, Any]],
256+
target: Target,
257+
vt_selection: list[VTSelection],
52258
*,
53-
scanner_params: Optional[dict[str, Any]] = None,
259+
scan_preferences: Optional[list[ScanPreference]] = None,
54260
) -> httpx.Response:
55261
"""
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.
57263
58264
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).
62268
63269
Returns:
64-
The full HTTP response of the POST /scans request.
270+
The full HTTP response returned by the POST /scans request.
65271
66272
Raises:
67273
httpx.HTTPStatusError: If the server responds with an error status
68274
and the exception is not suppressed.
69275
70276
See: POST /scans in the openvasd API documentation.
71277
"""
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)
75284

76285
try:
77286
response = self._client.post("/scans", json=request_json)
@@ -329,3 +538,25 @@ def stop(self, scan_id: ID) -> int:
329538
See: POST /scans/{id} with action=stop in the openvasd API documentation.
330539
"""
331540
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

Comments
 (0)