crowdsec-tracker-api 1.92.0__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.
- crowdsec_tracker_api/__init__.py +59 -0
- crowdsec_tracker_api/base_model.py +58 -0
- crowdsec_tracker_api/http_client.py +153 -0
- crowdsec_tracker_api/models.py +945 -0
- crowdsec_tracker_api/services/__init__.py +0 -0
- crowdsec_tracker_api/services/cves.py +179 -0
- crowdsec_tracker_api/services/integrations.py +172 -0
- crowdsec_tracker_api-1.92.0.dist-info/METADATA +35 -0
- crowdsec_tracker_api-1.92.0.dist-info/RECORD +12 -0
- crowdsec_tracker_api-1.92.0.dist-info/WHEEL +5 -0
- crowdsec_tracker_api-1.92.0.dist-info/licenses/LICENSE +21 -0
- crowdsec_tracker_api-1.92.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from .models import *
|
|
3
|
+
from .base_model import Page
|
|
4
|
+
from .services.integrations import Integrations
|
|
5
|
+
from .services.cves import Cves
|
|
6
|
+
from .http_client import ApiKeyAuth
|
|
7
|
+
|
|
8
|
+
class Server(Enum):
|
|
9
|
+
production_server = 'https://admin.api.crowdsec.net/v1'
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
'Integrations',
|
|
13
|
+
'Cves',
|
|
14
|
+
'ApiKeyCredentials',
|
|
15
|
+
'BasicAuthCredentials',
|
|
16
|
+
'BlocklistSubscription',
|
|
17
|
+
'CVESubscription',
|
|
18
|
+
'HTTPValidationError',
|
|
19
|
+
'IntegrationCreateRequest',
|
|
20
|
+
'IntegrationCreateResponse',
|
|
21
|
+
'IntegrationGetResponse',
|
|
22
|
+
'IntegrationGetResponsePage',
|
|
23
|
+
'IntegrationType',
|
|
24
|
+
'IntegrationUpdateRequest',
|
|
25
|
+
'IntegrationUpdateResponse',
|
|
26
|
+
'Links',
|
|
27
|
+
'OutputFormat',
|
|
28
|
+
'Stats',
|
|
29
|
+
'ValidationError',
|
|
30
|
+
'AffectedComponent',
|
|
31
|
+
'AllowlistSubscription',
|
|
32
|
+
'AttackDetail',
|
|
33
|
+
'Behavior',
|
|
34
|
+
'CVEEvent',
|
|
35
|
+
'CVEResponseBase',
|
|
36
|
+
'CVEsubscription',
|
|
37
|
+
'CWE',
|
|
38
|
+
'Classification',
|
|
39
|
+
'Classifications',
|
|
40
|
+
'EntityType',
|
|
41
|
+
'GetCVEIPsResponsePage',
|
|
42
|
+
'GetCVEResponse',
|
|
43
|
+
'GetCVESubscribedIntegrationsResponsePage',
|
|
44
|
+
'GetCVEsResponsePage',
|
|
45
|
+
'GetCVEsSortBy',
|
|
46
|
+
'GetCVEsSortOrder',
|
|
47
|
+
'History',
|
|
48
|
+
'IPItem',
|
|
49
|
+
'IntegrationResponse',
|
|
50
|
+
'Location',
|
|
51
|
+
'MitreTechnique',
|
|
52
|
+
'Reference',
|
|
53
|
+
'ScoreBreakdown',
|
|
54
|
+
'Scores',
|
|
55
|
+
'SubscribeCVEIntegrationRequest',
|
|
56
|
+
'ApiKeyAuth',
|
|
57
|
+
'Server',
|
|
58
|
+
'Page'
|
|
59
|
+
]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from urllib.parse import urlparse
|
|
2
|
+
from pydantic import BaseModel, ConfigDict, PrivateAttr, RootModel
|
|
3
|
+
from typing import Generic, Sequence, Optional, TypeVar, Any
|
|
4
|
+
from httpx import Auth
|
|
5
|
+
from .http_client import HttpClient
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BaseModelSdk(BaseModel):
|
|
9
|
+
model_config = ConfigDict(
|
|
10
|
+
extra="ignore",
|
|
11
|
+
)
|
|
12
|
+
_client: Optional["Service"] = PrivateAttr(default=None)
|
|
13
|
+
|
|
14
|
+
def __init__(self, /, _client: "Service" = None, **data):
|
|
15
|
+
super().__init__(**data)
|
|
16
|
+
self._client = _client
|
|
17
|
+
|
|
18
|
+
def next(self, client: "Service" = None) -> Optional["BaseModelSdk"]:
|
|
19
|
+
return (client if client is not None else self._client).next_page(self)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class RootModelSdk(RootModel):
|
|
23
|
+
def __getattr__(self, item: str) -> Any:
|
|
24
|
+
return getattr(self.root, item)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
T = TypeVar("T")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Page(BaseModelSdk, Generic[T]):
|
|
31
|
+
items: Sequence[T]
|
|
32
|
+
total: Optional[int]
|
|
33
|
+
page: Optional[int]
|
|
34
|
+
size: Optional[int]
|
|
35
|
+
pages: Optional[int] = None
|
|
36
|
+
links: Optional[dict] = None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Service:
|
|
40
|
+
def __init__(self, base_url: str, auth: Auth) -> None:
|
|
41
|
+
self.http_client = HttpClient(base_url=base_url, auth=auth)
|
|
42
|
+
|
|
43
|
+
def next_page(self, page: BaseModelSdk) -> Optional[BaseModelSdk]:
|
|
44
|
+
if not hasattr(page, "links") or not page.links:
|
|
45
|
+
raise ValueError(
|
|
46
|
+
"No links found in the response, this is not a paginated response."
|
|
47
|
+
)
|
|
48
|
+
if page.links.next:
|
|
49
|
+
# links are relative to host not to full base url. We need to pass a full formatted url here
|
|
50
|
+
parsed_url = urlparse(self.http_client.base_url)
|
|
51
|
+
response = self.http_client.get(
|
|
52
|
+
f"{parsed_url.scheme}://{parsed_url.netloc}{page.links.next}",
|
|
53
|
+
path_params=None,
|
|
54
|
+
params=None,
|
|
55
|
+
headers=None,
|
|
56
|
+
)
|
|
57
|
+
return page.__class__(_client=self, **response.json())
|
|
58
|
+
return None
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
from urllib.parse import quote, urlparse
|
|
2
|
+
|
|
3
|
+
from botocore.auth import SigV4Auth
|
|
4
|
+
from botocore.awsrequest import AWSRequest
|
|
5
|
+
import botocore.session
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AWSSignV4Auth(httpx.Auth):
|
|
11
|
+
def __init__(self, aws_region="eu-west-1") -> None:
|
|
12
|
+
self.aws_region = aws_region
|
|
13
|
+
|
|
14
|
+
def auth_flow(self, request):
|
|
15
|
+
request = self.sign_request(request)
|
|
16
|
+
yield request
|
|
17
|
+
|
|
18
|
+
def sign_request(self, request: httpx.Request) -> httpx.Request:
|
|
19
|
+
"""Signs an httpx request with AWS Signature Version 4."""
|
|
20
|
+
|
|
21
|
+
session = botocore.session.get_session()
|
|
22
|
+
credentials = session.get_credentials()
|
|
23
|
+
aws_request = AWSRequest(
|
|
24
|
+
method=request.method.upper(), url=str(request.url), data=request.content
|
|
25
|
+
)
|
|
26
|
+
region = self.aws_region
|
|
27
|
+
service = "execute-api"
|
|
28
|
+
|
|
29
|
+
# Sign the request
|
|
30
|
+
SigV4Auth(credentials, service, region).add_auth(aws_request)
|
|
31
|
+
|
|
32
|
+
# Update the httpx request headers with the signed headers
|
|
33
|
+
request.headers.update(dict(aws_request.headers))
|
|
34
|
+
|
|
35
|
+
return request
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ApiKeyAuth(httpx.Auth):
|
|
39
|
+
def __init__(self, api_key: str) -> None:
|
|
40
|
+
self.api_key = api_key
|
|
41
|
+
|
|
42
|
+
def auth_flow(self, request):
|
|
43
|
+
request.headers["x-api-key"] = self.api_key
|
|
44
|
+
yield request
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class HttpClient:
|
|
48
|
+
def __init__(self, base_url: str, auth: httpx.Auth, aws_region="eu-west-1") -> None:
|
|
49
|
+
self.aws_region = aws_region
|
|
50
|
+
self.base_url = base_url
|
|
51
|
+
self.auth = auth
|
|
52
|
+
self.client = httpx.Client(headers={"Accept-Encoding": "gzip"})
|
|
53
|
+
self.timeout = 30
|
|
54
|
+
|
|
55
|
+
def _replace_path_params(self, url: str, path_params: dict):
|
|
56
|
+
if path_params:
|
|
57
|
+
for param, value in path_params.items():
|
|
58
|
+
if not value:
|
|
59
|
+
raise ValueError(
|
|
60
|
+
f"Parameter {param} is required, cannot be empty or blank."
|
|
61
|
+
)
|
|
62
|
+
url = url.replace(f"{{{param}}}", quote(str(value)))
|
|
63
|
+
return url
|
|
64
|
+
|
|
65
|
+
def _normalize_url(self, url: str):
|
|
66
|
+
self.base_url = self.base_url.rstrip("/")
|
|
67
|
+
parsed_url = urlparse(url)
|
|
68
|
+
if not parsed_url.netloc:
|
|
69
|
+
return f"{self.base_url}{url}"
|
|
70
|
+
return url
|
|
71
|
+
|
|
72
|
+
def get(
|
|
73
|
+
self,
|
|
74
|
+
url: str,
|
|
75
|
+
path_params: dict = None,
|
|
76
|
+
params: dict = None,
|
|
77
|
+
headers: dict = None,
|
|
78
|
+
):
|
|
79
|
+
url = self._replace_path_params(
|
|
80
|
+
url=self._normalize_url(url), path_params=path_params
|
|
81
|
+
)
|
|
82
|
+
response = self.client.get(
|
|
83
|
+
url=url,
|
|
84
|
+
params=params,
|
|
85
|
+
headers=headers,
|
|
86
|
+
auth=self.auth,
|
|
87
|
+
timeout=self.timeout,
|
|
88
|
+
)
|
|
89
|
+
response.raise_for_status()
|
|
90
|
+
return response
|
|
91
|
+
|
|
92
|
+
def post(
|
|
93
|
+
self, url: str, path_params: dict, params: dict, headers: dict, json: dict
|
|
94
|
+
):
|
|
95
|
+
url = self._replace_path_params(
|
|
96
|
+
url=self._normalize_url(url), path_params=path_params
|
|
97
|
+
)
|
|
98
|
+
response = self.client.post(
|
|
99
|
+
url=url,
|
|
100
|
+
params=params,
|
|
101
|
+
headers=headers,
|
|
102
|
+
json=json,
|
|
103
|
+
auth=self.auth,
|
|
104
|
+
timeout=self.timeout,
|
|
105
|
+
)
|
|
106
|
+
response.raise_for_status()
|
|
107
|
+
return response
|
|
108
|
+
|
|
109
|
+
def put(self, url: str, path_params: dict, params: dict, headers: dict, json: dict):
|
|
110
|
+
url = self._replace_path_params(
|
|
111
|
+
url=self._normalize_url(url), path_params=path_params
|
|
112
|
+
)
|
|
113
|
+
response = self.client.put(
|
|
114
|
+
url=url,
|
|
115
|
+
params=params,
|
|
116
|
+
headers=headers,
|
|
117
|
+
json=json,
|
|
118
|
+
auth=self.auth,
|
|
119
|
+
timeout=self.timeout,
|
|
120
|
+
)
|
|
121
|
+
response.raise_for_status()
|
|
122
|
+
return response
|
|
123
|
+
|
|
124
|
+
def patch(
|
|
125
|
+
self, url: str, path_params: dict, params: dict, headers: dict, json: dict
|
|
126
|
+
):
|
|
127
|
+
url = self._replace_path_params(
|
|
128
|
+
url=self._normalize_url(url), path_params=path_params
|
|
129
|
+
)
|
|
130
|
+
response = self.client.patch(
|
|
131
|
+
url=url,
|
|
132
|
+
params=params,
|
|
133
|
+
headers=headers,
|
|
134
|
+
json=json,
|
|
135
|
+
auth=self.auth,
|
|
136
|
+
timeout=self.timeout,
|
|
137
|
+
)
|
|
138
|
+
response.raise_for_status()
|
|
139
|
+
return response
|
|
140
|
+
|
|
141
|
+
def delete(self, url: str, path_params: dict, params: dict, headers: dict):
|
|
142
|
+
url = self._replace_path_params(
|
|
143
|
+
url=self._normalize_url(url), path_params=path_params
|
|
144
|
+
)
|
|
145
|
+
response = self.client.delete(
|
|
146
|
+
url=url,
|
|
147
|
+
params=params,
|
|
148
|
+
headers=headers,
|
|
149
|
+
auth=self.auth,
|
|
150
|
+
timeout=self.timeout,
|
|
151
|
+
)
|
|
152
|
+
response.raise_for_status()
|
|
153
|
+
return response
|