Bez popisu

models.py 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. from datetime import datetime, timezone
  2. from typing import Any, Literal
  3. from pydantic import BaseModel, ConfigDict, Field
  4. def utc_now() -> datetime:
  5. return datetime.now(timezone.utc)
  6. class WazuhIngestRequest(BaseModel):
  7. model_config = ConfigDict(
  8. json_schema_extra={
  9. "example": {
  10. "source": "wazuh",
  11. "rule_id": "100335",
  12. "alert_id": "1737301024.88912",
  13. "severity": 12,
  14. "title": "VPN success outside Thailand",
  15. "payload": {"full_log": "soc_mvp_test=true usecase_id=A3-05"},
  16. }
  17. }
  18. )
  19. source: str = Field(default="wazuh", description="Alert source name.", examples=["wazuh"])
  20. rule_id: str | None = Field(default=None, description="Wazuh rule ID.", examples=["100335"])
  21. alert_id: str | None = Field(default=None, description="Original alert identifier.", examples=["1737301024.88912"])
  22. severity: int | None = Field(default=None, description="Numeric alert severity.", examples=[12])
  23. title: str | None = Field(default=None, description="Human-readable alert title.", examples=["VPN success outside Thailand"])
  24. payload: dict[str, Any] = Field(
  25. default_factory=dict,
  26. description="Raw alert payload from source.",
  27. examples=[{"full_log": "soc_mvp_test=true usecase_id=A3-05"}],
  28. )
  29. class ActionCreateIncidentRequest(BaseModel):
  30. model_config = ConfigDict(
  31. json_schema_extra={
  32. "example": {
  33. "title": "Suspicious admin login",
  34. "severity": "high",
  35. "source": "soc-integrator",
  36. "dedupe_key": "vpn-user1-2026-03-03",
  37. "payload": {"description": "Detected from C1-01", "case_customer": 1},
  38. }
  39. }
  40. )
  41. title: str = Field(description="Incident title for escalation target.", examples=["Suspicious admin login"])
  42. severity: str = Field(default="medium", description="Incident severity level.", examples=["high"])
  43. source: str = Field(default="soc-integrator", description="Producer of the incident.", examples=["soc-integrator"])
  44. dedupe_key: str | None = Field(default=None, description="Stable key to deduplicate incidents.", examples=["vpn-user1-2026-03-03"])
  45. payload: dict[str, Any] = Field(
  46. default_factory=dict,
  47. description="Additional incident context.",
  48. examples=[{"description": "Detected from C1-01", "case_customer": 1}],
  49. )
  50. class IrisTicketCreateRequest(BaseModel):
  51. model_config = ConfigDict(
  52. json_schema_extra={
  53. "example": {
  54. "title": "C1-01 impossible travel",
  55. "description": "Auto-created from SOC workflow",
  56. "case_customer": 1,
  57. "case_soc_id": "soc-prod",
  58. "payload": {"case_tags": ["appendix_c", "c1-01"]},
  59. }
  60. }
  61. )
  62. title: str = Field(description="IRIS case name.", examples=["C1-01 impossible travel"])
  63. description: str = Field(default="Created by soc-integrator", description="IRIS case description.", examples=["Auto-created from SOC workflow"])
  64. case_customer: int | None = Field(default=None, description="IRIS customer ID.", examples=[1])
  65. case_soc_id: str | None = Field(default=None, description="IRIS SOC ID.", examples=["soc-prod"])
  66. payload: dict[str, Any] = Field(
  67. default_factory=dict,
  68. description="Optional IRIS API fields merged into request body.",
  69. examples=[{"case_tags": ["appendix_c", "c1-01"]}],
  70. )
  71. class IocEnrichRequest(BaseModel):
  72. model_config = ConfigDict(
  73. json_schema_extra={
  74. "example": {
  75. "ioc_type": "ip",
  76. "ioc_value": "8.8.8.8",
  77. "providers": ["virustotal", "abuseipdb"],
  78. }
  79. }
  80. )
  81. ioc_type: Literal["domain", "ip", "hash", "url"] = Field(description="Type of IOC.", examples=["ip"])
  82. ioc_value: str = Field(description="IOC value to enrich.", examples=["8.8.8.8"])
  83. providers: list[str] = Field(
  84. default_factory=lambda: ["virustotal"],
  85. description="Enrichment providers to query.",
  86. examples=[["virustotal", "abuseipdb"]],
  87. )
  88. class IocEvaluateRequest(BaseModel):
  89. model_config = ConfigDict(
  90. json_schema_extra={
  91. "example": {
  92. "ioc_type": "domain",
  93. "ioc_value": "example.com",
  94. "providers": ["virustotal"],
  95. "malicious_threshold": 1,
  96. "suspicious_threshold": 3,
  97. }
  98. }
  99. )
  100. ioc_type: Literal["domain", "ip", "hash", "url"] = Field(description="Type of IOC.", examples=["domain"])
  101. ioc_value: str = Field(description="IOC value to evaluate.", examples=["example.com"])
  102. providers: list[str] = Field(default_factory=lambda: ["virustotal"], description="Evaluation providers.", examples=[["virustotal"]])
  103. malicious_threshold: int = Field(default=1, description="Minimum malicious engines to mark matched.", examples=[1])
  104. suspicious_threshold: int = Field(default=3, description="Minimum suspicious engines to mark matched.", examples=[3])
  105. class TriggerShuffleRequest(BaseModel):
  106. model_config = ConfigDict(
  107. json_schema_extra={
  108. "example": {
  109. "workflow_id": "07ecad05-ff68-41cb-888d-96d1a8e8db4b",
  110. "execution_argument": {"ioc_type": "ip", "ioc_value": "8.8.8.8"},
  111. }
  112. }
  113. )
  114. workflow_id: str = Field(description="Shuffle workflow UUID.", examples=["07ecad05-ff68-41cb-888d-96d1a8e8db4b"])
  115. execution_argument: dict[str, Any] = Field(
  116. default_factory=dict,
  117. description="Execution argument payload passed to workflow.",
  118. examples=[{"ioc_type": "ip", "ioc_value": "8.8.8.8"}],
  119. )
  120. class ShuffleProxyRequest(BaseModel):
  121. model_config = ConfigDict(
  122. json_schema_extra={
  123. "example": {
  124. "method": "POST",
  125. "path": "/api/v1/workflows",
  126. "params": {"limit": 50},
  127. "payload": {"name": "test"},
  128. }
  129. }
  130. )
  131. method: str = Field(default="GET", description="HTTP method for proxied request.", examples=["POST"])
  132. path: str = Field(description="Shuffle API path (with or without /api prefix).", examples=["/api/v1/workflows"])
  133. params: dict[str, Any] = Field(default_factory=dict, description="Query parameters.", examples=[{"limit": 50}])
  134. payload: dict[str, Any] = Field(default_factory=dict, description="JSON body for POST/PUT/PATCH.", examples=[{"name": "test"}])
  135. class ShuffleLoginRequest(BaseModel):
  136. model_config = ConfigDict(
  137. json_schema_extra={"example": {"username": "admin@example.com", "password": "change-me"}}
  138. )
  139. username: str = Field(description="Shuffle username.", examples=["admin@example.com"])
  140. password: str = Field(description="Shuffle password.", examples=["change-me"])
  141. class LogLossStreamCheck(BaseModel):
  142. model_config = ConfigDict(
  143. json_schema_extra={
  144. "example": {
  145. "name": "log_monitor",
  146. "query": "full_log:log_monitor OR rule.id:100411",
  147. "min_count": 1,
  148. }
  149. }
  150. )
  151. name: str = Field(description="Logical stream name.", examples=["fortigate"])
  152. query: str = Field(description="OpenSearch query to count matching logs.", examples=["full_log:*source=fortigate*"])
  153. min_count: int = Field(default=1, description="Minimum events expected in time window.", examples=[1])
  154. class LogLossCheckRequest(BaseModel):
  155. model_config = ConfigDict(
  156. json_schema_extra={
  157. "example": {
  158. "minutes": 5,
  159. "streams": [
  160. {"name": "log_monitor", "query": "full_log:log_monitor OR rule.id:100411", "min_count": 1},
  161. {
  162. "name": "fortigate",
  163. "query": "full_log:fortigate OR full_log:FGT80F OR full_log:FGT60F OR full_log:FGT40F OR full_log:FGT501E",
  164. "min_count": 1,
  165. },
  166. {"name": "windows_agent", "query": "full_log:windows_agent OR full_log:windows", "min_count": 1},
  167. ],
  168. }
  169. }
  170. )
  171. minutes: int = Field(default=5, description="Lookback window in minutes.", examples=[5])
  172. streams: list[LogLossStreamCheck] = Field(
  173. default_factory=list,
  174. description="Streams to validate. Empty uses default streams.",
  175. )
  176. class MvpIncidentIngestRequest(BaseModel):
  177. model_config = ConfigDict(
  178. json_schema_extra={
  179. "example": {
  180. "source": "wazuh",
  181. "event_type": "vpn_geo_anomaly",
  182. "event_id": "evt-1737301024",
  183. "timestamp": "2026-03-03T06:00:00Z",
  184. "severity": "high",
  185. "title": "VPN login anomaly",
  186. "description": "User logged in from unexpected country",
  187. "asset": {"user": "alice", "hostname": "win-dc01"},
  188. "network": {"src_ip": "8.8.8.8", "country": "US"},
  189. "tags": ["vpn", "geo-anomaly"],
  190. "risk_context": {"admin_account": True},
  191. "raw": {"full_log": "..."},
  192. "payload": {"case_customer": 1},
  193. }
  194. }
  195. )
  196. source: Literal["wazuh", "shuffle", "manual"] = Field(default="wazuh", description="Source system for the event.", examples=["wazuh"])
  197. event_type: Literal[
  198. "ioc_dns",
  199. "ioc_ips",
  200. "vpn_geo_anomaly",
  201. "auth_anomaly",
  202. "c1_impossible_travel",
  203. "c2_credential_abuse",
  204. "c3_lateral_movement",
  205. "generic",
  206. ] = Field(default="generic", description="Normalized event category.", examples=["vpn_geo_anomaly"])
  207. event_id: str = Field(description="Unique event identifier from source.", examples=["evt-1737301024"])
  208. timestamp: datetime = Field(description="Event timestamp in ISO-8601 format.", examples=["2026-03-03T06:00:00Z"])
  209. severity: Literal["low", "medium", "high", "critical"] = Field(default="medium", description="Normalized severity.", examples=["high"])
  210. title: str = Field(description="Short event title.", examples=["VPN login anomaly"])
  211. description: str = Field(description="Detailed event description.", examples=["User logged in from unexpected country"])
  212. asset: dict[str, Any] = Field(default_factory=dict, description="Asset and identity context.", examples=[{"user": "alice", "hostname": "win-dc01"}])
  213. network: dict[str, Any] = Field(default_factory=dict, description="Network context.", examples=[{"src_ip": "8.8.8.8", "country": "US"}])
  214. tags: list[str] = Field(default_factory=list, description="Search/filter tags.", examples=[["vpn", "geo-anomaly"]])
  215. risk_context: dict[str, Any] = Field(default_factory=dict, description="Risk indicators used in scoring.", examples=[{"admin_account": True}])
  216. raw: dict[str, Any] = Field(default_factory=dict, description="Original raw event object.", examples=[{"full_log": "..." }])
  217. payload: dict[str, Any] = Field(default_factory=dict, description="Additional pipeline payload.", examples=[{"case_customer": 1}])
  218. class MvpIocEvaluateRequest(BaseModel):
  219. model_config = ConfigDict(
  220. json_schema_extra={
  221. "example": {
  222. "ioc_type": "ip",
  223. "ioc_value": "1.1.1.1",
  224. "source_event": {"event_id": "evt-1", "asset": {"hostname": "fw01"}},
  225. }
  226. }
  227. )
  228. ioc_type: Literal["domain", "ip"] = Field(description="IOC type for MVP workflow.", examples=["ip"])
  229. ioc_value: str = Field(description="IOC value to evaluate.", examples=["1.1.1.1"])
  230. source_event: dict[str, Any] = Field(
  231. default_factory=dict,
  232. description="Source event context used for incident linking.",
  233. examples=[{"event_id": "evt-1", "asset": {"hostname": "fw01"}}],
  234. )
  235. class MvpVpnEvaluateRequest(BaseModel):
  236. model_config = ConfigDict(
  237. json_schema_extra={
  238. "example": {
  239. "user": "alice",
  240. "src_ip": "8.8.8.8",
  241. "country_code": "US",
  242. "success": True,
  243. "event_time": "2026-03-03T06:00:00Z",
  244. "is_admin": False,
  245. "off_hours": True,
  246. "first_seen_country": True,
  247. "event_id": "vpn-1737301024",
  248. }
  249. }
  250. )
  251. user: str = Field(description="Authenticated username.", examples=["alice"])
  252. src_ip: str = Field(description="Source public IP address.", examples=["8.8.8.8"])
  253. country_code: str = Field(description="ISO country code of source IP.", examples=["US"])
  254. success: bool = Field(description="Whether VPN login succeeded.", examples=[True])
  255. event_time: datetime = Field(description="Authentication timestamp.", examples=["2026-03-03T06:00:00Z"])
  256. is_admin: bool = Field(default=False, description="User is privileged/admin account.", examples=[False])
  257. off_hours: bool = Field(default=False, description="Event happened outside business hours.", examples=[True])
  258. first_seen_country: bool = Field(default=False, description="First observed country for user.", examples=[True])
  259. event_id: str | None = Field(default=None, description="Optional event ID from upstream.", examples=["vpn-1737301024"])
  260. class CDetectionEvaluateRequest(BaseModel):
  261. model_config = ConfigDict(
  262. json_schema_extra={
  263. "example": {
  264. "minutes": 30,
  265. "query": "soc_mvp_test=true",
  266. "selectors": ["c1", "c3"],
  267. "dry_run": True,
  268. "limit": 200,
  269. }
  270. }
  271. )
  272. minutes: int = Field(default=30, description="Lookback window in minutes.", examples=[30])
  273. query: str = Field(default="soc_mvp_test=true", description="OpenSearch query for candidate events.", examples=["soc_mvp_test=true"])
  274. selectors: list[str] = Field(
  275. default_factory=list,
  276. description="Optional use-case filters (c1/c2/c3 or Cx-yy).",
  277. examples=[["c1", "c3"]],
  278. )
  279. dry_run: bool = Field(default=False, description="If true, evaluate only and do not create incidents/tickets.", examples=[True])
  280. limit: int = Field(default=200, description="Maximum events fetched from Wazuh indexer.", examples=[200])
  281. class SimLogRunRequest(BaseModel):
  282. model_config = ConfigDict(
  283. json_schema_extra={
  284. "example": {
  285. "script": "fortigate",
  286. "target": "all",
  287. "scenario": "all",
  288. "count": 1,
  289. "delay_seconds": 0.3,
  290. "forever": False,
  291. }
  292. }
  293. )
  294. script: Literal[
  295. "fortigate",
  296. "endpoint",
  297. "cisco",
  298. "proposal_required",
  299. "proposal_appendix_b",
  300. "proposal_appendix_c",
  301. "wazuh_test",
  302. ] = Field(description="Whitelisted simulator script key.", examples=["fortigate"])
  303. target: str = Field(default="all", description="Primary selector/model/platform for the script.", examples=["all"])
  304. scenario: str = Field(default="all", description="Secondary selector used by endpoint simulator.", examples=["process"])
  305. count: int = Field(default=1, description="Number of iterations for non-forever mode.", examples=[1])
  306. delay_seconds: float = Field(default=0.3, description="Delay between iterations.", examples=[0.3])
  307. forever: bool = Field(default=False, description="Run continuously until stopped.", examples=[False])
  308. class IrisAlertCreateRequest(BaseModel):
  309. model_config = ConfigDict(
  310. json_schema_extra={
  311. "example": {
  312. "title": "Suspicious login detected",
  313. "description": "Multiple failed logins followed by success from unusual IP",
  314. "severity_id": 4,
  315. "status_id": 2,
  316. "source": "wazuh",
  317. "source_ref": "wazuh-alert-12345",
  318. "customer_id": 1,
  319. "payload": {"alert_tags": "brute-force,authentication"},
  320. }
  321. }
  322. )
  323. title: str = Field(description="Alert title.", examples=["Suspicious login detected"])
  324. description: str = Field(default="Created by soc-integrator", description="Alert description.")
  325. severity_id: int = Field(default=3, description="IRIS severity ID (1=Info,2=Low,3=Medium,4=High,5=Critical).", examples=[4])
  326. status_id: int = Field(default=2, description="IRIS alert status ID.", examples=[2])
  327. source: str = Field(default="soc-integrator", description="Alert source name.", examples=["wazuh"])
  328. source_ref: str | None = Field(default=None, description="Source-system reference ID.", examples=["wazuh-alert-12345"])
  329. customer_id: int | None = Field(default=None, description="IRIS customer ID (defaults to configured value).")
  330. payload: dict[str, Any] = Field(default_factory=dict, description="Additional IRIS alert fields merged into the request.")
  331. class ApiResponse(BaseModel):
  332. ok: bool = True
  333. message: str = "ok"
  334. timestamp: datetime = Field(default_factory=utc_now)
  335. data: dict[str, Any] = Field(default_factory=dict)