security-use 0.1.1__py3-none-any.whl → 0.2.9__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.
- security_use/__init__.py +9 -1
- security_use/auth/__init__.py +16 -0
- security_use/auth/client.py +223 -0
- security_use/auth/config.py +177 -0
- security_use/auth/oauth.py +317 -0
- security_use/cli.py +699 -34
- security_use/compliance/__init__.py +10 -0
- security_use/compliance/mapper.py +275 -0
- security_use/compliance/models.py +50 -0
- security_use/dependency_scanner.py +76 -30
- security_use/fixers/iac_fixer.py +173 -95
- security_use/iac/rules/azure.py +246 -0
- security_use/iac/rules/gcp.py +255 -0
- security_use/iac/rules/kubernetes.py +429 -0
- security_use/iac/rules/registry.py +56 -0
- security_use/parsers/__init__.py +18 -0
- security_use/parsers/base.py +2 -0
- security_use/parsers/composer.py +101 -0
- security_use/parsers/conda.py +97 -0
- security_use/parsers/dotnet.py +89 -0
- security_use/parsers/gradle.py +90 -0
- security_use/parsers/maven.py +108 -0
- security_use/parsers/npm.py +196 -0
- security_use/parsers/yarn.py +108 -0
- security_use/reporter.py +29 -1
- security_use/sbom/__init__.py +10 -0
- security_use/sbom/generator.py +340 -0
- security_use/sbom/models.py +40 -0
- security_use/scanner.py +15 -2
- security_use/sensor/__init__.py +125 -0
- security_use/sensor/alert_queue.py +207 -0
- security_use/sensor/config.py +217 -0
- security_use/sensor/dashboard_alerter.py +246 -0
- security_use/sensor/detector.py +415 -0
- security_use/sensor/endpoint_analyzer.py +339 -0
- security_use/sensor/middleware.py +521 -0
- security_use/sensor/models.py +140 -0
- security_use/sensor/webhook.py +227 -0
- security_use-0.2.9.dist-info/METADATA +531 -0
- security_use-0.2.9.dist-info/RECORD +60 -0
- security_use-0.2.9.dist-info/licenses/LICENSE +21 -0
- security_use-0.1.1.dist-info/METADATA +0 -92
- security_use-0.1.1.dist-info/RECORD +0 -30
- {security_use-0.1.1.dist-info → security_use-0.2.9.dist-info}/WHEEL +0 -0
- {security_use-0.1.1.dist-info → security_use-0.2.9.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""Webhook alerter for sending security alerts."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from .models import ActionTaken, AlertPayload, AlertResponse, SecurityEvent
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class WebhookAlerter:
|
|
16
|
+
"""Sends security alerts to a webhook endpoint."""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
webhook_url: str,
|
|
21
|
+
retry_count: int = 3,
|
|
22
|
+
retry_delay: float = 1.0,
|
|
23
|
+
timeout: float = 10.0,
|
|
24
|
+
headers: Optional[dict[str, str]] = None,
|
|
25
|
+
):
|
|
26
|
+
"""Initialize the webhook alerter.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
webhook_url: URL to send alerts to.
|
|
30
|
+
retry_count: Number of retry attempts on failure.
|
|
31
|
+
retry_delay: Delay in seconds between retries (doubles each attempt).
|
|
32
|
+
timeout: Request timeout in seconds.
|
|
33
|
+
headers: Additional headers to include in requests.
|
|
34
|
+
"""
|
|
35
|
+
self.webhook_url = webhook_url
|
|
36
|
+
self.retry_count = retry_count
|
|
37
|
+
self.retry_delay = retry_delay
|
|
38
|
+
self.timeout = timeout
|
|
39
|
+
self.headers = headers or {}
|
|
40
|
+
|
|
41
|
+
async def send_alert(
|
|
42
|
+
self,
|
|
43
|
+
event: SecurityEvent,
|
|
44
|
+
action_taken: ActionTaken = ActionTaken.LOGGED,
|
|
45
|
+
) -> AlertResponse:
|
|
46
|
+
"""Send a security alert to the webhook.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
event: The security event to report.
|
|
50
|
+
action_taken: What action was taken in response.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
AlertResponse with success status and details.
|
|
54
|
+
"""
|
|
55
|
+
payload = AlertPayload(
|
|
56
|
+
timestamp=datetime.utcnow(),
|
|
57
|
+
alert=event,
|
|
58
|
+
action_taken=action_taken,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
attempt = 0
|
|
62
|
+
last_error: Optional[str] = None
|
|
63
|
+
last_status = 0
|
|
64
|
+
|
|
65
|
+
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
|
66
|
+
while attempt <= self.retry_count:
|
|
67
|
+
try:
|
|
68
|
+
response = await client.post(
|
|
69
|
+
self.webhook_url,
|
|
70
|
+
json=payload.to_dict(),
|
|
71
|
+
headers={
|
|
72
|
+
"Content-Type": "application/json",
|
|
73
|
+
"User-Agent": "security-use-sensor/1.0",
|
|
74
|
+
**self.headers,
|
|
75
|
+
},
|
|
76
|
+
)
|
|
77
|
+
last_status = response.status_code
|
|
78
|
+
|
|
79
|
+
if 200 <= response.status_code < 300:
|
|
80
|
+
logger.info(
|
|
81
|
+
"Alert sent successfully: %s (attempt %d)",
|
|
82
|
+
event.event_type.value,
|
|
83
|
+
attempt + 1,
|
|
84
|
+
)
|
|
85
|
+
return AlertResponse(
|
|
86
|
+
success=True,
|
|
87
|
+
webhook_status=response.status_code,
|
|
88
|
+
retry_count=attempt,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Non-success status code
|
|
92
|
+
last_error = f"HTTP {response.status_code}: {response.text[:200]}"
|
|
93
|
+
logger.warning(
|
|
94
|
+
"Webhook returned %d (attempt %d/%d): %s",
|
|
95
|
+
response.status_code,
|
|
96
|
+
attempt + 1,
|
|
97
|
+
self.retry_count + 1,
|
|
98
|
+
response.text[:100],
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
except httpx.TimeoutException as e:
|
|
102
|
+
last_error = f"Timeout: {e}"
|
|
103
|
+
logger.warning(
|
|
104
|
+
"Webhook timeout (attempt %d/%d): %s",
|
|
105
|
+
attempt + 1,
|
|
106
|
+
self.retry_count + 1,
|
|
107
|
+
e,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
except httpx.RequestError as e:
|
|
111
|
+
last_error = f"Request error: {e}"
|
|
112
|
+
logger.warning(
|
|
113
|
+
"Webhook request error (attempt %d/%d): %s",
|
|
114
|
+
attempt + 1,
|
|
115
|
+
self.retry_count + 1,
|
|
116
|
+
e,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
attempt += 1
|
|
120
|
+
if attempt <= self.retry_count:
|
|
121
|
+
delay = self.retry_delay * (2 ** (attempt - 1))
|
|
122
|
+
await asyncio.sleep(delay)
|
|
123
|
+
|
|
124
|
+
logger.error(
|
|
125
|
+
"Failed to send alert after %d attempts: %s",
|
|
126
|
+
self.retry_count + 1,
|
|
127
|
+
last_error,
|
|
128
|
+
)
|
|
129
|
+
return AlertResponse(
|
|
130
|
+
success=False,
|
|
131
|
+
webhook_status=last_status,
|
|
132
|
+
retry_count=attempt - 1,
|
|
133
|
+
error_message=last_error,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def send_alert_sync(
|
|
137
|
+
self,
|
|
138
|
+
event: SecurityEvent,
|
|
139
|
+
action_taken: ActionTaken = ActionTaken.LOGGED,
|
|
140
|
+
) -> AlertResponse:
|
|
141
|
+
"""Synchronous version of send_alert for non-async contexts.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
event: The security event to report.
|
|
145
|
+
action_taken: What action was taken in response.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
AlertResponse with success status and details.
|
|
149
|
+
"""
|
|
150
|
+
payload = AlertPayload(
|
|
151
|
+
timestamp=datetime.utcnow(),
|
|
152
|
+
alert=event,
|
|
153
|
+
action_taken=action_taken,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
attempt = 0
|
|
157
|
+
last_error: Optional[str] = None
|
|
158
|
+
last_status = 0
|
|
159
|
+
|
|
160
|
+
with httpx.Client(timeout=self.timeout) as client:
|
|
161
|
+
while attempt <= self.retry_count:
|
|
162
|
+
try:
|
|
163
|
+
response = client.post(
|
|
164
|
+
self.webhook_url,
|
|
165
|
+
json=payload.to_dict(),
|
|
166
|
+
headers={
|
|
167
|
+
"Content-Type": "application/json",
|
|
168
|
+
"User-Agent": "security-use-sensor/1.0",
|
|
169
|
+
**self.headers,
|
|
170
|
+
},
|
|
171
|
+
)
|
|
172
|
+
last_status = response.status_code
|
|
173
|
+
|
|
174
|
+
if 200 <= response.status_code < 300:
|
|
175
|
+
logger.info(
|
|
176
|
+
"Alert sent successfully: %s (attempt %d)",
|
|
177
|
+
event.event_type.value,
|
|
178
|
+
attempt + 1,
|
|
179
|
+
)
|
|
180
|
+
return AlertResponse(
|
|
181
|
+
success=True,
|
|
182
|
+
webhook_status=response.status_code,
|
|
183
|
+
retry_count=attempt,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
last_error = f"HTTP {response.status_code}: {response.text[:200]}"
|
|
187
|
+
logger.warning(
|
|
188
|
+
"Webhook returned %d (attempt %d/%d)",
|
|
189
|
+
response.status_code,
|
|
190
|
+
attempt + 1,
|
|
191
|
+
self.retry_count + 1,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
except httpx.TimeoutException as e:
|
|
195
|
+
last_error = f"Timeout: {e}"
|
|
196
|
+
logger.warning(
|
|
197
|
+
"Webhook timeout (attempt %d/%d)",
|
|
198
|
+
attempt + 1,
|
|
199
|
+
self.retry_count + 1,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
except httpx.RequestError as e:
|
|
203
|
+
last_error = f"Request error: {e}"
|
|
204
|
+
logger.warning(
|
|
205
|
+
"Webhook request error (attempt %d/%d)",
|
|
206
|
+
attempt + 1,
|
|
207
|
+
self.retry_count + 1,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
attempt += 1
|
|
211
|
+
if attempt <= self.retry_count:
|
|
212
|
+
import time
|
|
213
|
+
|
|
214
|
+
delay = self.retry_delay * (2 ** (attempt - 1))
|
|
215
|
+
time.sleep(delay)
|
|
216
|
+
|
|
217
|
+
logger.error(
|
|
218
|
+
"Failed to send alert after %d attempts: %s",
|
|
219
|
+
self.retry_count + 1,
|
|
220
|
+
last_error,
|
|
221
|
+
)
|
|
222
|
+
return AlertResponse(
|
|
223
|
+
success=False,
|
|
224
|
+
webhook_status=last_status,
|
|
225
|
+
retry_count=attempt - 1,
|
|
226
|
+
error_message=last_error,
|
|
227
|
+
)
|