Skip to content

Commit 3a7eab7

Browse files
ozgenbjoernricks
authored andcommitted
Add: Add new HTTP API structure and introduce modular sub-APIs
1 parent 6092792 commit 3a7eab7

File tree

15 files changed

+1795
-2
lines changed

15 files changed

+1795
-2
lines changed

gvm/protocols/http/openvasd/__init__.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
# SPDX-License-Identifier: GPL-3.0-or-later
44

55
"""
6-
Package for sending openvasd and handling the responses of HTTP API requests.
6+
Package for sending requests to openvasd and handling HTTP API responses.
77
8-
* :class:`OpenvasdHttpApiV1` - openvasd version 1
8+
Modules:
9+
- :class:`OpenvasdHttpApiV1` – Main class for communicating with OpenVASD API v1.
10+
11+
Usage:
12+
from gvm.protocols.http.openvasd import OpenvasdHttpApiV1
913
"""
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# SPDX-FileCopyrightText: 2025 Greenbone AG
2+
#
3+
# SPDX-License-Identifier: GPL-3.0-or-later
4+
5+
"""
6+
Client wrapper for initializing a connection to the openvasd HTTP API using optional mTLS authentication.
7+
"""
8+
9+
import ssl
10+
from typing import Optional, Tuple, Union
11+
12+
from httpx import Client
13+
14+
15+
class OpenvasdClient:
16+
"""
17+
The client wrapper around `httpx.Client` configured for mTLS-secured access or API KEY
18+
to an openvasd HTTP API instance.
19+
"""
20+
21+
def __init__(
22+
self,
23+
host_name: str,
24+
*,
25+
api_key: Optional[str] = None,
26+
server_ca_path: Optional[str] = None,
27+
client_cert_paths: Optional[Union[str, Tuple[str, str]]] = None,
28+
port: int = 3000,
29+
):
30+
"""
31+
Initialize the OpenVASD HTTP client with optional mTLS and API key.
32+
33+
Args:
34+
host_name: Hostname or IP of the OpenVASD server (e.g., "localhost").
35+
api_key: Optional API key used for authentication via HTTP headers.
36+
server_ca_path: Path to the server's CA certificate (for verifying the server).
37+
client_cert_paths: Path to the client certificate (str) or a tuple of
38+
(cert_path, key_path) for mTLS authentication.
39+
port: The port to connect to (default: 3000).
40+
41+
Behavior:
42+
- If both `server_ca_path` and `client_cert_paths` are set, an mTLS connection
43+
is established using an SSLContext.
44+
- If not, `verify` is set to False (insecure), and HTTP is used instead of HTTPS.
45+
HTTP connection needs api_key for authorization.
46+
"""
47+
headers = {}
48+
49+
context: Optional[ssl.SSLContext] = None
50+
51+
# Prepare mTLS SSL context if needed
52+
if client_cert_paths and server_ca_path:
53+
context = ssl.create_default_context(
54+
ssl.Purpose.SERVER_AUTH, cafile=server_ca_path
55+
)
56+
if isinstance(client_cert_paths, tuple):
57+
context.load_cert_chain(
58+
certfile=client_cert_paths[0], keyfile=client_cert_paths[1]
59+
)
60+
else:
61+
context.load_cert_chain(certfile=client_cert_paths)
62+
63+
context.check_hostname = False
64+
context.verify_mode = ssl.CERT_REQUIRED
65+
66+
# Set verify based on context presence
67+
verify: Union[bool, ssl.SSLContext] = context if context else False
68+
69+
if api_key:
70+
headers["X-API-KEY"] = api_key
71+
72+
protocol = "https" if context else "http"
73+
base_url = f"{protocol}://{host_name}:{port}"
74+
75+
self.client = Client(
76+
base_url=base_url,
77+
headers=headers,
78+
verify=verify,
79+
http2=True,
80+
timeout=10.0,
81+
)

gvm/protocols/http/openvasd/health.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# SPDX-FileCopyrightText: 2025 Greenbone AG
2+
#
3+
# SPDX-License-Identifier: GPL-3.0-or-later
4+
5+
"""
6+
API wrapper for accessing the /health endpoints of the openvasd HTTP API.
7+
"""
8+
9+
import httpx
10+
11+
12+
class HealthAPI:
13+
"""
14+
Provides access to the openvasd /health endpoints, which expose the
15+
operational state of the scanner.
16+
17+
All methods return the HTTP status code of the response and raise an exception
18+
if the server returns an error response (4xx or 5xx).
19+
"""
20+
21+
def __init__(self, client: httpx.Client):
22+
"""
23+
Create a new HealthAPI instance.
24+
25+
Args:
26+
client: An initialized `httpx.Client` configured for communicating
27+
with the openvasd server.
28+
"""
29+
self._client = client
30+
31+
def get_alive(self, safe: bool = False) -> int:
32+
"""
33+
Check if the scanner process is alive.
34+
35+
Args:
36+
safe: If True, suppress exceptions and return structured error responses.
37+
38+
Returns:
39+
HTTP status code (e.g., 200 if alive).
40+
41+
Raises:
42+
httpx.HTTPStatusError: If the server response indicates failure and safe is False.
43+
44+
See: GET /health/alive in the openvasd API documentation.
45+
"""
46+
try:
47+
response = self._client.get("/health/alive")
48+
response.raise_for_status()
49+
return response.status_code
50+
except httpx.HTTPStatusError as e:
51+
if safe:
52+
return e.response.status_code
53+
raise
54+
55+
def get_ready(self, safe: bool = False) -> int:
56+
"""
57+
Check if the scanner is ready to accept requests (e.g., feed loaded).
58+
59+
Args:
60+
safe: If True, suppress exceptions and return structured error responses.
61+
62+
Returns:
63+
HTTP status code (e.g., 200 if ready).
64+
65+
Raises:
66+
httpx.HTTPStatusError: If the server response indicates failure and safe is False.
67+
68+
See: GET /health/ready in the openvasd API documentation.
69+
"""
70+
try:
71+
response = self._client.get("/health/ready")
72+
response.raise_for_status()
73+
return response.status_code
74+
except httpx.HTTPStatusError as e:
75+
if safe:
76+
return e.response.status_code
77+
raise
78+
79+
def get_started(self, safe: bool = False) -> int:
80+
"""
81+
Check if the scanner has fully started.
82+
83+
Args:
84+
safe: If True, suppress exceptions and return structured error responses.
85+
86+
Returns:
87+
HTTP status code (e.g., 200 if started).
88+
89+
Raises:
90+
httpx.HTTPStatusError: If the server response indicates failure and safe is False.
91+
92+
See: GET /health/started in the openvasd API documentation.
93+
"""
94+
try:
95+
response = self._client.get("/health/started")
96+
response.raise_for_status()
97+
return response.status_code
98+
except httpx.HTTPStatusError as e:
99+
if safe:
100+
return e.response.status_code
101+
raise
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# SPDX-FileCopyrightText: 2025 Greenbone AG
2+
#
3+
# SPDX-License-Identifier: GPL-3.0-or-later
4+
5+
"""
6+
API wrapper for retrieving metadata from the openvasd HTTP API using HEAD requests.
7+
"""
8+
9+
import httpx
10+
11+
12+
class MetadataAPI:
13+
"""
14+
Provides access to metadata endpoints exposed by the openvasd server
15+
using lightweight HTTP HEAD requests.
16+
17+
These endpoints return useful information in HTTP headers such as:
18+
- API version
19+
- Feed version
20+
- Authentication type
21+
22+
If the scanner is protected and the request is unauthorized, a 401 response
23+
is handled gracefully.
24+
"""
25+
26+
def __init__(self, client: httpx.Client):
27+
"""
28+
Initialize a MetadataAPI instance.
29+
30+
Args:
31+
client: An `httpx.Client` configured to communicate with the openvasd server.
32+
"""
33+
self._client = client
34+
35+
def get(self, safe: bool = False) -> dict:
36+
"""
37+
Perform a HEAD request to `/` to retrieve top-level API metadata.
38+
39+
Args:
40+
safe: If True, suppress exceptions and return structured error responses.
41+
42+
Returns:
43+
A dictionary containing:
44+
45+
- "api-version"
46+
- "feed-version"
47+
- "authentication"
48+
49+
Or if safe=True and error occurs:
50+
51+
- {"error": str, "status_code": int}
52+
53+
Raises:
54+
httpx.HTTPStatusError: For non-401 HTTP errors if safe=False.
55+
56+
See: HEAD / in the openvasd API documentation.
57+
"""
58+
try:
59+
response = self._client.head("/")
60+
response.raise_for_status()
61+
return {
62+
"api-version": response.headers.get("api-version"),
63+
"feed-version": response.headers.get("feed-version"),
64+
"authentication": response.headers.get("authentication"),
65+
}
66+
except httpx.HTTPStatusError as e:
67+
if safe:
68+
return {"error": str(e), "status_code": e.response.status_code}
69+
raise
70+
71+
def get_scans(self, safe: bool = False) -> dict:
72+
"""
73+
Perform a HEAD request to `/scans` to retrieve scan endpoint metadata.
74+
75+
Args:
76+
safe: If True, suppress exceptions and return structured error responses.
77+
78+
Returns:
79+
A dictionary containing:
80+
81+
- "api-version"
82+
- "feed-version"
83+
- "authentication"
84+
85+
Or if safe=True and error occurs:
86+
87+
- {"error": str, "status_code": int}
88+
89+
Raises:
90+
httpx.HTTPStatusError: For non-401 HTTP errors if safe=False.
91+
92+
See: HEAD /scans in the openvasd API documentation.
93+
"""
94+
try:
95+
response = self._client.head("/scans")
96+
response.raise_for_status()
97+
return {
98+
"api-version": response.headers.get("api-version"),
99+
"feed-version": response.headers.get("feed-version"),
100+
"authentication": response.headers.get("authentication"),
101+
}
102+
except httpx.HTTPStatusError as e:
103+
if safe:
104+
return {"error": str(e), "status_code": e.response.status_code}
105+
raise

gvm/protocols/http/openvasd/notus.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# SPDX-FileCopyrightText: 2025 Greenbone AG
2+
#
3+
# SPDX-License-Identifier: GPL-3.0-or-later
4+
5+
"""
6+
API wrapper for interacting with the Notus component of the openvasd HTTP API.
7+
"""
8+
9+
import urllib.parse
10+
from typing import List
11+
12+
import httpx
13+
14+
15+
class NotusAPI:
16+
"""
17+
Provides access to the Notus-related endpoints of the openvasd HTTP API.
18+
19+
This includes retrieving supported operating systems and triggering
20+
package-based vulnerability scans for a specific OS.
21+
"""
22+
23+
def __init__(self, client: httpx.Client):
24+
"""
25+
Initialize a NotusAPI instance.
26+
27+
Args:
28+
client: An `httpx.Client` configured to communicate with the openvasd server.
29+
"""
30+
self._client = client
31+
32+
def get_os_list(self, safe: bool = False) -> httpx.Response:
33+
"""
34+
Retrieve the list of supported operating systems from the Notus service.
35+
36+
Args:
37+
safe: If True, return error info on failure instead of raising.
38+
39+
Returns:
40+
The full `httpx.Response` on success.
41+
42+
See: GET /notus in the openvasd API documentation.
43+
"""
44+
try:
45+
response = self._client.get("/notus")
46+
response.raise_for_status()
47+
return response
48+
except httpx.HTTPStatusError as e:
49+
if safe:
50+
return e.response
51+
raise
52+
53+
def run_scan(
54+
self, os: str, package_list: List[str], safe: bool = False
55+
) -> httpx.Response:
56+
"""
57+
Trigger a Notus scan for a given OS and list of packages.
58+
59+
Args:
60+
os: Operating system name (e.g., "debian", "alpine").
61+
package_list: List of package names to evaluate for vulnerabilities.
62+
safe: If True, return error info on failure instead of raising.
63+
64+
Returns:
65+
The full `httpx.Response` on success.
66+
67+
See: POST /notus/{os} in the openvasd API documentation.
68+
"""
69+
quoted_os = urllib.parse.quote(os)
70+
try:
71+
response = self._client.post(
72+
f"/notus/{quoted_os}", json=package_list
73+
)
74+
response.raise_for_status()
75+
return response
76+
except httpx.HTTPStatusError as e:
77+
if safe:
78+
return e.response
79+
raise

0 commit comments

Comments
 (0)