>
475
|
+ ioc_value=payload.ioc_value,
|
|
|
476
|
+ providers=providers,
|
|
|
477
|
+ request_payload=payload.model_dump(mode="json"),
|
|
|
478
|
+ response_payload=result,
|
|
|
479
|
+ )
|
|
|
480
|
+ return ApiResponse(data={"ioc": result})
|
|
|
481
|
+
|
|
|
482
|
+
|
|
|
483
|
+@app.post("/ioc/evaluate", response_model=ApiResponse)
|
|
|
484
|
+async def ioc_evaluate(payload: IocEvaluateRequest) -> ApiResponse:
|
|
|
485
|
+ providers = [p.lower().strip() for p in payload.providers]
|
|
|
486
|
+ supported = {"virustotal", "abuseipdb"}
|
|
|
487
|
+ requested = [p for p in providers if p in supported]
|
|
|
488
|
+ if not requested:
|
|
|
489
|
+ raise HTTPException(status_code=400, detail="No supported provider requested. Use ['virustotal'] or ['abuseipdb'].")
|
|
|
490
|
+
|
|
|
491
|
+ per_provider: dict[str, dict[str, object]] = {}
|
|
|
492
|
+ errors: dict[str, str] = {}
|
|
|
493
|
+
|
|
|
494
|
+ if "virustotal" in requested:
|
|
|
495
|
+ try:
|
|
|
496
|
+ vt = await virustotal_adapter.enrich_ioc(payload.ioc_type, payload.ioc_value)
|
|
|
497
|
+ vt_result, _, _, _ = _build_vt_ioc_result(
|
|
|
498
|
+ vt=vt,
|
|
|
499
|
+ ioc_type=payload.ioc_type,
|
|
|
500
|
+ ioc_value=payload.ioc_value,
|
|
|
501
|
+ malicious_threshold=payload.malicious_threshold,
|
|
|
502
|
+ suspicious_threshold=payload.suspicious_threshold,
|
|
|
503
|
+ )
|
|
|
504
|
+ per_provider["virustotal"] = vt_result
|
|
|
505
|
+ except Exception as exc:
|
|
|
506
|
+ errors["virustotal"] = str(exc)
|
|
|
507
|
+
|
|
|
508
|
+ if "abuseipdb" in requested:
|
|
|
509
|
+ if payload.ioc_type != "ip":
|
|
|
510
|
+ errors["abuseipdb"] = "AbuseIPDB supports ioc_type='ip' only"
|
|
|
511
|
+ else:
|
|
|
512
|
+ try:
|
|
|
513
|
+ abuse = await abuseipdb_adapter.check_ip(payload.ioc_value)
|
|
|
514
|
+ abuse_result, _, _, _ = _build_abuseipdb_ioc_result(
|
|
|
515
|
+ abuse=abuse,
|
|
|
516
|
+ ioc_value=payload.ioc_value,
|
|
|
517
|
+ confidence_threshold=50,
|
|
|
518
|
+ )
|
|
|
519
|
+ per_provider["abuseipdb"] = abuse_result
|
|
|
520
|
+ except Exception as exc:
|
|
|
521
|
+ errors["abuseipdb"] = str(exc)
|
|
|
522
|
+
|
|
|
523
|
+ if not per_provider:
|
|
|
524
|
+ repo.add_ioc_trace(
|
|
|
525
|
+ action="evaluate",
|
|
|
526
|
+ ioc_type=payload.ioc_type,
|
|
|
527
|
+ ioc_value=payload.ioc_value,
|
|
|
528
|
+ providers=requested,
|
|
|
529
|
+ request_payload=payload.model_dump(mode="json"),
|
|
|
530
|
+ response_payload={},
|
|
|
531
|
+ error=str(errors),
|
|
|
532
|
+ )
|
|
|
533
|
+ raise HTTPException(status_code=502, detail=f"Provider evaluation failed: {errors}")
|
|
|
534
|
+
|
|
|
535
|
+ # aggregate decision (max confidence/severity, matched if any provider matched)
|
|
|
536
|
+ order = {"low": 1, "medium": 2, "high": 3, "critical": 4}
|
|
|
537
|
+ matched = any(bool(r.get("matched")) for r in per_provider.values())
|
|
|
538
|
+ confidence = max(float(r.get("confidence", 0.0) or 0.0) for r in per_provider.values())
|
|
|
539
|
+ severity = max((str(r.get("severity", "low")) for r in per_provider.values()), key=lambda x: order.get(x, 1))
|
|
|
540
|
+ reason_parts = [f"{name}:{res.get('reason','')}" for name, res in per_provider.items()]
|
|
|
541
|
+ if errors:
|
|
|
542
|
+ reason_parts.append(f"errors={errors}")
|
|
|
543
|
+ ioc_result = {
|
|
|
544
|
+ "ioc_type": payload.ioc_type,
|
|
|
545
|
+ "ioc_value": payload.ioc_value,
|
|
|
546
|
+ "matched": matched,
|
|
|
547
|
+ "severity": severity,
|
|
|
548
|
+ "confidence": round(confidence, 4),
|
|
|
549
|
+ "reason": " | ".join(reason_parts),
|
|
|
550
|
+ "providers": per_provider,
|
|
|
551
|
+ }
|
|
|
552
|
+
|
|
|
553
|
+ repo.add_ioc_trace(
|
|
|
554
|
+ action="evaluate",
|
|
|
555
|
+ ioc_type=payload.ioc_type,
|
|
|
556
|
+ ioc_value=payload.ioc_value,
|
|
|
557
|
+ providers=providers,
|
|
|
558
|
+ request_payload=payload.model_dump(mode="json"),
|
|
|
559
|
+ response_payload=ioc_result,
|
|
|
560
|
+ matched=matched,
|
|
|
561
|
+ severity=severity,
|
|
|
562
|
+ confidence=float(ioc_result["confidence"]),
|
|
|
563
|
+ )
|
|
|
564
|
+
|
|
|
565
|
+ return ApiResponse(data={"ioc": ioc_result})
|
|
|
566
|
+
|
|
|
567
|
+
|
|
|
568
|
+@app.post("/ioc/upload-file", response_model=ApiResponse)
|
|
|
569
|
+async def ioc_upload_file(file: UploadFile = File(...)) -> ApiResponse:
|
|
|
570
|
+ content = await file.read()
|
|
|
571
|
+ if not content:
|
|
|
572
|
+ raise HTTPException(status_code=400, detail="Uploaded file is empty")
|
|
|
573
|
+ try:
|
|
|
574
|
+ vt_upload = await virustotal_adapter.upload_file(file.filename or "upload.bin", content)
|
|
|
575
|
+ except Exception as exc:
|
|
|
576
|
+ repo.add_ioc_trace(
|
|
|
577
|
+ action="upload_file",
|
|
|
578
|
+ ioc_type="hash",
|
|
|
579
|
+ ioc_value=file.filename or "<unknown>",
|
|
|
580
|
+ providers=["virustotal"],
|
|
|
581
|
+ request_payload={"filename": file.filename, "size": len(content)},
|
|
|
582
|
+ response_payload={},
|
|
|
583
|
+ error=str(exc),
|
|
|
584
|
+ )
|
|
|
585
|
+ raise HTTPException(status_code=502, detail=f"VirusTotal upload failed: {exc}") from exc
|
|
|
586
|
+
|
|
|
587
|
+ repo.add_ioc_trace(
|
|
|
588
|
+ action="upload_file",
|
|
|
589
|
+ ioc_type="hash",
|
|
|
590
|
+ ioc_value=file.filename or "<unknown>",
|
|
|
591
|
+ providers=["virustotal"],
|
|
|
592
|
+ request_payload={"filename": file.filename, "size": len(content)},
|
|
|
593
|
+ response_payload=vt_upload if isinstance(vt_upload, dict) else {"raw": str(vt_upload)},
|
|
|
594
|
+ )
|
|
|
595
|
+ return ApiResponse(data={"virustotal": vt_upload})
|
|
|
596
|
+
|
|
|
597
|
+
|
|
|
598
|
+@app.get("/ioc/analysis/{analysis_id}", response_model=ApiResponse)
|
|
|
599
|
+async def ioc_get_analysis(analysis_id: str) -> ApiResponse:
|
|
|
600
|
+ try:
|
|
|
601
|
+ vt_analysis = await virustotal_adapter.get_analysis(analysis_id)
|
|
|
602
|
+ except Exception as exc:
|
|
|
603
|
+ repo.add_ioc_trace(
|
|
|
604
|
+ action="analysis",
|
|
|
605
|
+ ioc_type="hash",
|
|
|
606
|
+ ioc_value=analysis_id,
|
|
|
607
|
+ providers=["virustotal"],
|
|
|
608
|
+ request_payload={"analysis_id": analysis_id},
|
|
|
609
|
+ response_payload={},
|
|
|
610
|
+ error=str(exc),
|
|
|
611
|
+ )
|
|
|
612
|
+ raise HTTPException(status_code=502, detail=f"VirusTotal analysis fetch failed: {exc}") from exc
|
|
|
613
|
+
|
|
|
614
|
+ repo.add_ioc_trace(
|
|
|
615
|
+ action="analysis",
|
|
|
616
|
+ ioc_type="hash",
|
|
|
617
|
+ ioc_value=analysis_id,
|
|
|
618
|
+ providers=["virustotal"],
|
|
|
619
|
+ request_payload={"analysis_id": analysis_id},
|
|
|
620
|
+ response_payload=vt_analysis if isinstance(vt_analysis, dict) else {"raw": str(vt_analysis)},
|
|
|
621
|
+ )
|
|
|
622
|
+ return ApiResponse(data={"virustotal": vt_analysis})
|
|
|
623
|
+
|
|
|
624
|
+
|
|
|
625
|
+@app.post("/ioc/evaluate-file", response_model=ApiResponse)
|
|
|
626
|
+async def ioc_evaluate_file(
|
|
|
627
|
+ file: UploadFile = File(...),
|
|
|
628
|
+ malicious_threshold: int = 1,
|
|
|
629
|
+ suspicious_threshold: int = 3,
|
|
|
630
|
+ poll_timeout_seconds: int = 30,
|
|
|
631
|
+ poll_interval_seconds: int = 2,
|
|
|
632
|
+) -> ApiResponse:
|
|
|
633
|
+ content = await file.read()
|
|
|
634
|
+ if not content:
|
|
|
635
|
+ raise HTTPException(status_code=400, detail="Uploaded file is empty")
|
|
|
636
|
+
|
|
|
637
|
+ try:
|
|
|
638
|
+ vt_upload = await virustotal_adapter.upload_file(file.filename or "upload.bin", content)
|
|
|
639
|
+ except Exception as exc:
|
|
|
640
|
+ repo.add_ioc_trace(
|
|
|
641
|
+ action="evaluate_file",
|
|
|
642
|
+ ioc_type="hash",
|
|
|
643
|
+ ioc_value=file.filename or "<unknown>",
|
|
|
644
|
+ providers=["virustotal"],
|
|
|
645
|
+ request_payload={"filename": file.filename, "size": len(content)},
|
|
|
646
|
+ response_payload={},
|
|
|
647
|
+ error=str(exc),
|
|
|
648
|
+ )
|
|
|
649
|
+ raise HTTPException(status_code=502, detail=f"VirusTotal upload failed: {exc}") from exc
|
|
|
650
|
+
|
|
|
651
|
+ analysis_id = (
|
|
|
652
|
+ (((vt_upload.get("data") or {}).get("id")) if isinstance(vt_upload, dict) else None)
|
|
|
653
|
+ or ""
|
|
|
654
|
+ )
|
|
|
655
|
+ if not analysis_id:
|
|
|
656
|
+ raise HTTPException(status_code=502, detail="VirusTotal upload response missing analysis ID")
|
|
|
657
|
+
|
|
|
658
|
+ timeout = max(1, poll_timeout_seconds)
|
|
|
659
|
+ interval = max(1, poll_interval_seconds)
|
|
|
660
|
+ elapsed = 0
|
|
|
661
|
+ analysis: dict[str, object] = {}
|
|
|
662
|
+ while elapsed <= timeout:
|
|
|
663
|
+ analysis = await virustotal_adapter.get_analysis(analysis_id)
|
|
|
664
|
+ status = (
|
|
|
665
|
+ (((analysis.get("data") or {}).get("attributes") or {}).get("status"))
|
|
|
666
|
+ if isinstance(analysis, dict)
|
|
|
667
|
+ else None
|
|
|
668
|
+ )
|
|
|
669
|
+ if status == "completed":
|
|
|
670
|
+ break
|
|
|
671
|
+ await asyncio.sleep(interval)
|
|
|
672
|
+ elapsed += interval
|
|
|
673
|
+
|
|
|
674
|
+ sha256 = (
|
|
|
675
|
+ (((analysis.get("meta") or {}).get("file_info") or {}).get("sha256"))
|
|
|
676
|
+ if isinstance(analysis, dict)
|
|
|
677
|
+ else None
|
|
|
678
|
+ )
|
|
|
679
|
+ if not sha256:
|
|
|
680
|
+ raise HTTPException(status_code=502, detail="VirusTotal analysis did not return file hash yet")
|
|
|
681
|
+
|
|
|
682
|
+ try:
|
|
|
683
|
+ vt_file = await virustotal_adapter.enrich_ioc("hash", str(sha256))
|
|
|
684
|
+ except Exception as exc:
|
|
|
685
|
+ repo.add_ioc_trace(
|
|
|
686
|
+ action="evaluate_file",
|
|
|
687
|
+ ioc_type="hash",
|
|
|
688
|
+ ioc_value=str(sha256),
|
|
|
689
|
+ providers=["virustotal"],
|
|
|
690
|
+ request_payload={"filename": file.filename, "analysis_id": analysis_id},
|
|
|
691
|
+ response_payload={"upload": vt_upload, "analysis": analysis},
|
|
|
692
|
+ error=str(exc),
|
|
|
693
|
+ )
|
|
|
694
|
+ raise HTTPException(status_code=502, detail=f"VirusTotal report fetch failed: {exc}") from exc
|
|
|
695
|
+
|
|
|
696
|
+ ioc_result, matched, severity, confidence = _build_vt_ioc_result(
|
|
|
697
|
+ vt=vt_file,
|
|
|
698
|
+ ioc_type="hash",
|
|
|
699
|
+ ioc_value=str(sha256),
|
|
|
700
|
+ malicious_threshold=malicious_threshold,
|
|
|
701
|
+ suspicious_threshold=suspicious_threshold,
|
|
|
702
|
+ )
|
|
|
703
|
+ ioc_result["analysis_id"] = analysis_id
|
|
|
704
|
+ ioc_result["filename"] = file.filename
|
|
|
705
|
+
|
|
|
706
|
+ repo.add_ioc_trace(
|
|
|
707
|
+ action="evaluate_file",
|
|
|
708
|
+ ioc_type="hash",
|
|
|
709
|
+ ioc_value=str(sha256),
|
|
|
710
|
+ providers=["virustotal"],
|
|
|
711
|
+ request_payload={"filename": file.filename, "analysis_id": analysis_id},
|
|
|
712
|
+ response_payload={
|
|
|
713
|
+ "upload": vt_upload,
|
|
|
714
|
+ "analysis": analysis,
|
|
|
715
|
+ "ioc": ioc_result,
|
|
|
716
|
+ },
|
|
|
717
|
+ matched=matched,
|
|
|
718
|
+ severity=severity,
|
|
|
719
|
+ confidence=confidence,
|
|
|
720
|
+ )
|
|
|
721
|
+ return ApiResponse(data={"ioc": ioc_result, "analysis": analysis, "upload": vt_upload})
|
|
|
722
|
+
|
|
|
723
|
+
|
|
|
724
|
+@app.get("/ioc/history", response_model=ApiResponse)
|
|
|
725
|
+async def ioc_history(limit: int = 50, offset: int = 0) -> ApiResponse:
|
|
|
726
|
+ return ApiResponse(data={"items": repo.list_ioc_trace(limit=limit, offset=offset)})
|
|
|
727
|
+
|
|
|
728
|
+
|
|
300
|
729
|
@app.get("/sync/wazuh-version", response_model=ApiResponse)
|
|
301
|
730
|
async def sync_wazuh_version() -> ApiResponse:
|
|
302
|
731
|
try:
|
|
|
@@ -25,6 +25,28 @@ class ActionCreateIncidentRequest(BaseModel):
|
|
25
|
25
|
payload: dict[str, Any] = Field(default_factory=dict)
|
|
26
|
26
|
|
|
27
|
27
|
|
|
|
28
|
+class IrisTicketCreateRequest(BaseModel):
|
|
|
29
|
+ title: str
|
|
|
30
|
+ description: str = "Created by soc-integrator"
|
|
|
31
|
+ case_customer: int | None = None
|
|
|
32
|
+ case_soc_id: str | None = None
|
|
|
33
|
+ payload: dict[str, Any] = Field(default_factory=dict)
|
|
|
34
|
+
|
|
|
35
|
+
|
|
|
36
|
+class IocEnrichRequest(BaseModel):
|
|
|
37
|
+ ioc_type: Literal["domain", "ip", "hash", "url"]
|
|
|
38
|
+ ioc_value: str
|
|
|
39
|
+ providers: list[str] = Field(default_factory=lambda: ["virustotal"])
|
|
|
40
|
+
|
|
|
41
|
+
|
|
|
42
|
+class IocEvaluateRequest(BaseModel):
|
|
|
43
|
+ ioc_type: Literal["domain", "ip", "hash", "url"]
|
|
|
44
|
+ ioc_value: str
|
|
|
45
|
+ providers: list[str] = Field(default_factory=lambda: ["virustotal"])
|
|
|
46
|
+ malicious_threshold: int = 1
|
|
|
47
|
+ suspicious_threshold: int = 3
|
|
|
48
|
+
|
|
|
49
|
+
|
|
28
|
50
|
class TriggerShuffleRequest(BaseModel):
|
|
29
|
51
|
workflow_id: str
|
|
30
|
52
|
execution_argument: dict[str, Any] = Field(default_factory=dict)
|
|
|
@@ -151,3 +151,52 @@ class MvpRepository:
|
|
151
|
151
|
""",
|
|
152
|
152
|
(incident_key, status_code, success, response_excerpt),
|
|
153
|
153
|
)
|
|
|
154
|
+
|
|
|
155
|
+ def add_ioc_trace(
|
|
|
156
|
+ self,
|
|
|
157
|
+ action: str,
|
|
|
158
|
+ ioc_type: str,
|
|
|
159
|
+ ioc_value: str,
|
|
|
160
|
+ providers: list[str],
|
|
|
161
|
+ request_payload: dict[str, Any],
|
|
|
162
|
+ response_payload: dict[str, Any],
|
|
|
163
|
+ matched: bool | None = None,
|
|
|
164
|
+ severity: str | None = None,
|
|
|
165
|
+ confidence: float | None = None,
|
|
|
166
|
+ error: str | None = None,
|
|
|
167
|
+ ) -> None:
|
|
|
168
|
+ with get_conn() as conn, conn.cursor() as cur:
|
|
|
169
|
+ cur.execute(
|
|
|
170
|
+ """
|
|
|
171
|
+ INSERT INTO ioc_trace(
|
|
|
172
|
+ action, ioc_type, ioc_value, providers,
|
|
|
173
|
+ request_payload, response_payload, matched, severity, confidence, error
|
|
|
174
|
+ )
|
|
|
175
|
+ VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
|
|
176
|
+ """,
|
|
|
177
|
+ (
|
|
|
178
|
+ action,
|
|
|
179
|
+ ioc_type,
|
|
|
180
|
+ ioc_value,
|
|
|
181
|
+ Json(providers),
|
|
|
182
|
+ Json(request_payload),
|
|
|
183
|
+ Json(response_payload),
|
|
|
184
|
+ matched,
|
|
|
185
|
+ severity,
|
|
|
186
|
+ confidence,
|
|
|
187
|
+ error,
|
|
|
188
|
+ ),
|
|
|
189
|
+ )
|
|
|
190
|
+
|
|
|
191
|
+ def list_ioc_trace(self, limit: int = 50, offset: int = 0) -> list[dict[str, Any]]:
|
|
|
192
|
+ with get_conn() as conn, conn.cursor() as cur:
|
|
|
193
|
+ cur.execute(
|
|
|
194
|
+ """
|
|
|
195
|
+ SELECT id, action, ioc_type, ioc_value, providers, matched, severity, confidence, error, created_at
|
|
|
196
|
+ FROM ioc_trace
|
|
|
197
|
+ ORDER BY created_at DESC
|
|
|
198
|
+ LIMIT %s OFFSET %s
|
|
|
199
|
+ """,
|
|
|
200
|
+ (max(1, limit), max(0, offset)),
|
|
|
201
|
+ )
|
|
|
202
|
+ return [dict(row) for row in cur.fetchall()]
|
|
|
@@ -25,3 +25,22 @@ This script demonstrates:
|
|
25
|
25
|
|
|
26
|
26
|
1. Direct call to `IRIS /api/v2/cases`
|
|
27
|
27
|
2. Call through `soc-integrator /action/create-iris-case`
|
|
|
28
|
+
|
|
|
29
|
+## Send sample event to Shuffle webhook
|
|
|
30
|
+
|
|
|
31
|
+Use this helper with the sample workflow:
|
|
|
32
|
+
|
|
|
33
|
+- `/Users/simplicoltd./projects/soc/shuffle-workflows/sample-webhook-soc-integrator-iris-workflow.json`
|
|
|
34
|
+
|
|
|
35
|
+Run:
|
|
|
36
|
+
|
|
|
37
|
+```bash
|
|
|
38
|
+SHUFFLE_WEBHOOK_URL='http://localhost:3001/api/v1/hooks/webhook_xxx' \
|
|
|
39
|
+bash soc-integrator/examples/send_to_shuffle_webhook.sh
|
|
|
40
|
+```
|
|
|
41
|
+
|
|
|
42
|
+Environment variables:
|
|
|
43
|
+
|
|
|
44
|
+- `SHUFFLE_WEBHOOK_URL` (required)
|
|
|
45
|
+- `INTEGRATOR_URL` (default: `http://localhost:8088`)
|
|
|
46
|
+- `INTERNAL_KEY` (optional)
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+#!/usr/bin/env bash
|
|
|
2
|
+set -euo pipefail
|
|
|
3
|
+
|
|
|
4
|
+SHUFFLE_WEBHOOK_URL="${SHUFFLE_WEBHOOK_URL:-}"
|
|
|
5
|
+INTEGRATOR_URL="${INTEGRATOR_URL:-http://localhost:8088}"
|
|
|
6
|
+INTERNAL_KEY="${INTERNAL_KEY:-}"
|
|
|
7
|
+
|
|
|
8
|
+if [[ -z "${SHUFFLE_WEBHOOK_URL}" ]]; then
|
|
|
9
|
+ echo "error: SHUFFLE_WEBHOOK_URL is required"
|
|
|
10
|
+ echo "example:"
|
|
|
11
|
+ echo " SHUFFLE_WEBHOOK_URL='http://localhost:3001/api/v1/hooks/webhook_...' \\"
|
|
|
12
|
+ echo " bash soc-integrator/examples/send_to_shuffle_webhook.sh"
|
|
|
13
|
+ exit 1
|
|
|
14
|
+fi
|
|
|
15
|
+
|
|
|
16
|
+curl -sS -X POST "${SHUFFLE_WEBHOOK_URL}" \
|
|
|
17
|
+ -H "Content-Type: application/json" \
|
|
|
18
|
+ -d "{
|
|
|
19
|
+ \"event_id\": \"soc-integrator-test-$(date +%s)\",
|
|
|
20
|
+ \"source\": \"soc-integrator\",
|
|
|
21
|
+ \"severity\": \"high\",
|
|
|
22
|
+ \"title\": \"Suspicious VPN login outside Thailand\",
|
|
|
23
|
+ \"description\": \"Detected by soc-integrator test script\",
|
|
|
24
|
+ \"integrator_url\": \"${INTEGRATOR_URL}\",
|
|
|
25
|
+ \"internal_key\": \"${INTERNAL_KEY}\"
|
|
|
26
|
+ }"
|
|
|
27
|
+
|
|
|
28
|
+echo
|
|
|
29
|
+echo "sent webhook payload to Shuffle"
|
|
|
@@ -3,3 +3,4 @@ uvicorn==0.35.0
|
|
3
|
3
|
httpx==0.28.1
|
|
4
|
4
|
pydantic-settings==2.10.1
|
|
5
|
5
|
psycopg[binary]==3.2.1
|
|
|
6
|
+python-multipart==0.0.20
|
|
|
@@ -1,6 +0,0 @@
|
|
1
|
|
-WAZUH_VERSION=4.14.3
|
|
2
|
|
-WAZUH_IMAGE_VERSION=4.14.3
|
|
3
|
|
-WAZUH_TAG_REVISION=1
|
|
4
|
|
-FILEBEAT_TEMPLATE_BRANCH=4.14.3
|
|
5
|
|
-WAZUH_FILEBEAT_MODULE=wazuh-filebeat-0.5.tar.gz
|
|
6
|
|
-WAZUH_UI_REVISION=1
|