nexus-fleet 1.0.0__tar.gz

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.
Files changed (31) hide show
  1. nexus_fleet-1.0.0/PKG-INFO +80 -0
  2. nexus_fleet-1.0.0/README.md +63 -0
  3. nexus_fleet-1.0.0/nexus_agent/__init__.py +2 -0
  4. nexus_fleet-1.0.0/nexus_agent/__main__.py +61 -0
  5. nexus_fleet-1.0.0/nexus_agent/agent.py +351 -0
  6. nexus_fleet-1.0.0/nexus_agent/collectors.py +319 -0
  7. nexus_fleet-1.0.0/nexus_cli/__init__.py +1 -0
  8. nexus_fleet-1.0.0/nexus_cli/__main__.py +100 -0
  9. nexus_fleet-1.0.0/nexus_cli/admin.py +46 -0
  10. nexus_fleet-1.0.0/nexus_cli/menu.py +328 -0
  11. nexus_fleet-1.0.0/nexus_common/__init__.py +1 -0
  12. nexus_fleet-1.0.0/nexus_common/log.py +31 -0
  13. nexus_fleet-1.0.0/nexus_common/protocol.py +158 -0
  14. nexus_fleet-1.0.0/nexus_common/schema.py +185 -0
  15. nexus_fleet-1.0.0/nexus_dashboard/__init__.py +1 -0
  16. nexus_fleet-1.0.0/nexus_dashboard/app.js +162 -0
  17. nexus_fleet-1.0.0/nexus_dashboard/index.html +71 -0
  18. nexus_fleet-1.0.0/nexus_dashboard/server.py +35 -0
  19. nexus_fleet-1.0.0/nexus_dashboard/style.css +48 -0
  20. nexus_fleet-1.0.0/nexus_fleet.egg-info/PKG-INFO +80 -0
  21. nexus_fleet-1.0.0/nexus_fleet.egg-info/SOURCES.txt +29 -0
  22. nexus_fleet-1.0.0/nexus_fleet.egg-info/dependency_links.txt +1 -0
  23. nexus_fleet-1.0.0/nexus_fleet.egg-info/entry_points.txt +5 -0
  24. nexus_fleet-1.0.0/nexus_fleet.egg-info/top_level.txt +5 -0
  25. nexus_fleet-1.0.0/nexus_manager/__init__.py +2 -0
  26. nexus_fleet-1.0.0/nexus_manager/__main__.py +53 -0
  27. nexus_fleet-1.0.0/nexus_manager/rules.py +206 -0
  28. nexus_fleet-1.0.0/nexus_manager/server.py +837 -0
  29. nexus_fleet-1.0.0/nexus_manager/sigma.py +71 -0
  30. nexus_fleet-1.0.0/pyproject.toml +45 -0
  31. nexus_fleet-1.0.0/setup.cfg +4 -0
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.4
2
+ Name: nexus-fleet
3
+ Version: 1.0.0
4
+ Summary: Nexus Fleet — agent/manager/cli/dashboard keamanan endpoint (ala-Wazuh, ringan, stdlib-only)
5
+ Author: Nexus Security
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/your-org/nexus-fleet
8
+ Project-URL: Issues, https://github.com/your-org/nexus-fleet/issues
9
+ Keywords: security,soc,edr,agent,manager,monitoring,wazuh,fleet
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Topic :: Security
14
+ Classifier: Environment :: Console
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+
18
+ # Nexus Fleet — Security Platform (agent · manager · cli · dashboard)
19
+
20
+ **1 platform, 4 komponen** ala-Wazuh, stdlib-only (Python 3.8+). Amankan **jaringan, server,
21
+ dan website** dari satu titik: agent ringan mengirim telemetri keamanan ke manager pusat yang
22
+ menjalankan **rule engine → alert** (level 0–15 + MITRE ATT&CK), lalu ditampilkan di dashboard/CLI.
23
+
24
+ ```
25
+ endpoint ──(HMAC/HTTP)──► nexus-manager ──► SQLite (events, alerts, audit)
26
+ nexus-agent FIM·SCA· rule+alert engine ▲ ▲
27
+ inventory·webaudit·ports policy·retention │ │
28
+ ◄── policy/perintah ── nexus-dashboard nexus-cli
29
+ ```
30
+
31
+ ## Install
32
+
33
+ **pip (disarankan):**
34
+ ```bash
35
+ pip install . # dari folder ini; memasang 4 perintah:
36
+ # nexus-manager · nexus-agent · nexus-cli · nexus-dashboard
37
+ ```
38
+ **npm (wrapper Node → Python):**
39
+ ```bash
40
+ npm install -g . # perintah sama; butuh Python 3.8+ di PATH
41
+ ```
42
+ **tanpa install:** `cd python/fleet && python -m nexus_manager run` (dst.).
43
+
44
+ ## Pakai
45
+
46
+ ```bash
47
+ # 1) Manager (server pusat + dashboard di http://host:8765/)
48
+ nexus-manager run --host 0.0.0.0 --port 8765
49
+ nexus-manager info # enrollment key + admin token
50
+
51
+ # 2) Agent di tiap endpoint
52
+ nexus-agent enroll --host <manager> --port 8765 --key <ENROLL_KEY> --labels prod,web
53
+ nexus-agent start # daemon: FIM/SCA/inventory/webaudit/ports/...
54
+
55
+ # 3) Admin / SOC
56
+ nexus-cli # console interaktif: menu Network & Website security
57
+ nexus-cli --token <ADMIN_TOKEN> alerts # alert (rule engine + MITRE + rekomendasi)
58
+ nexus-cli --token <ADMIN_TOKEN> ack --id <ALERT_ID> --status resolved
59
+ nexus-cli --token <ADMIN_TOKEN> report # report konsisten (schema nexus.report/v1)
60
+ nexus-dashboard --port 8080 # (opsional) host dashboard di port terpisah
61
+ ```
62
+
63
+ ## Service (jalan saat boot)
64
+ - **Linux:** `deploy/systemd/nexus-{manager,agent}.service` → `systemctl enable --now`.
65
+ - **Windows:** `deploy/windows/install-agent-service.ps1` (Scheduled Task, SYSTEM, AtStartup).
66
+
67
+ ## Konsep kunci
68
+ - **Skema baku** (`nexus_common/schema.py`): event/alert/report seragam, condong OCSF, `origin: real|demo`.
69
+ - **Real findings only**: manager menolak event `demo` secara default (`accept_demo=0`).
70
+ - **Rule engine** (`nexus_manager/rules.py`): rule native + MITRE + rekomendasi + response; bisa di-push.
71
+ - **Alert engine**: dedup anti-fatigue, ack/resolve, retensi, audit log.
72
+ - **Keamanan**: HMAC per-agent, enrollment key, admin token; HTTP LAN (offline-first).
73
+
74
+ Uji end-to-end: `python ../tests/test_fleet.py` (21 seksi).
75
+
76
+ > Roadmap menuju standar industri penuh: agent Go/Rust, OpenSearch/Postgres, mTLS/gRPC,
77
+ > import Sigma, YARA, Active Response, OCSF penuh, AI remediation, RBAC multi-tenant.
78
+ > Pembeda: developer-first (audit Laravel/React/Next, parser log app, security posture score).
79
+
80
+ *For Personal / Ethical Hacking Study Only.*
@@ -0,0 +1,63 @@
1
+ # Nexus Fleet — Security Platform (agent · manager · cli · dashboard)
2
+
3
+ **1 platform, 4 komponen** ala-Wazuh, stdlib-only (Python 3.8+). Amankan **jaringan, server,
4
+ dan website** dari satu titik: agent ringan mengirim telemetri keamanan ke manager pusat yang
5
+ menjalankan **rule engine → alert** (level 0–15 + MITRE ATT&CK), lalu ditampilkan di dashboard/CLI.
6
+
7
+ ```
8
+ endpoint ──(HMAC/HTTP)──► nexus-manager ──► SQLite (events, alerts, audit)
9
+ nexus-agent FIM·SCA· rule+alert engine ▲ ▲
10
+ inventory·webaudit·ports policy·retention │ │
11
+ ◄── policy/perintah ── nexus-dashboard nexus-cli
12
+ ```
13
+
14
+ ## Install
15
+
16
+ **pip (disarankan):**
17
+ ```bash
18
+ pip install . # dari folder ini; memasang 4 perintah:
19
+ # nexus-manager · nexus-agent · nexus-cli · nexus-dashboard
20
+ ```
21
+ **npm (wrapper Node → Python):**
22
+ ```bash
23
+ npm install -g . # perintah sama; butuh Python 3.8+ di PATH
24
+ ```
25
+ **tanpa install:** `cd python/fleet && python -m nexus_manager run` (dst.).
26
+
27
+ ## Pakai
28
+
29
+ ```bash
30
+ # 1) Manager (server pusat + dashboard di http://host:8765/)
31
+ nexus-manager run --host 0.0.0.0 --port 8765
32
+ nexus-manager info # enrollment key + admin token
33
+
34
+ # 2) Agent di tiap endpoint
35
+ nexus-agent enroll --host <manager> --port 8765 --key <ENROLL_KEY> --labels prod,web
36
+ nexus-agent start # daemon: FIM/SCA/inventory/webaudit/ports/...
37
+
38
+ # 3) Admin / SOC
39
+ nexus-cli # console interaktif: menu Network & Website security
40
+ nexus-cli --token <ADMIN_TOKEN> alerts # alert (rule engine + MITRE + rekomendasi)
41
+ nexus-cli --token <ADMIN_TOKEN> ack --id <ALERT_ID> --status resolved
42
+ nexus-cli --token <ADMIN_TOKEN> report # report konsisten (schema nexus.report/v1)
43
+ nexus-dashboard --port 8080 # (opsional) host dashboard di port terpisah
44
+ ```
45
+
46
+ ## Service (jalan saat boot)
47
+ - **Linux:** `deploy/systemd/nexus-{manager,agent}.service` → `systemctl enable --now`.
48
+ - **Windows:** `deploy/windows/install-agent-service.ps1` (Scheduled Task, SYSTEM, AtStartup).
49
+
50
+ ## Konsep kunci
51
+ - **Skema baku** (`nexus_common/schema.py`): event/alert/report seragam, condong OCSF, `origin: real|demo`.
52
+ - **Real findings only**: manager menolak event `demo` secara default (`accept_demo=0`).
53
+ - **Rule engine** (`nexus_manager/rules.py`): rule native + MITRE + rekomendasi + response; bisa di-push.
54
+ - **Alert engine**: dedup anti-fatigue, ack/resolve, retensi, audit log.
55
+ - **Keamanan**: HMAC per-agent, enrollment key, admin token; HTTP LAN (offline-first).
56
+
57
+ Uji end-to-end: `python ../tests/test_fleet.py` (21 seksi).
58
+
59
+ > Roadmap menuju standar industri penuh: agent Go/Rust, OpenSearch/Postgres, mTLS/gRPC,
60
+ > import Sigma, YARA, Active Response, OCSF penuh, AI remediation, RBAC multi-tenant.
61
+ > Pembeda: developer-first (audit Laravel/React/Next, parser log app, security posture score).
62
+
63
+ *For Personal / Ethical Hacking Study Only.*
@@ -0,0 +1,2 @@
1
+ """nexus-agent — daemon endpoint ringan Nexus Fleet (stdlib-only, copy-deployable)."""
2
+ from . import agent # noqa: F401
@@ -0,0 +1,61 @@
1
+ # nexus_agent/__main__.py
2
+ """
3
+ nexus-agent — entrypoint standalone (daemon endpoint ringan).
4
+
5
+ python -m nexus_agent enroll --host <manager> --port 8765 --key <ENROLL_KEY> [--name myhost]
6
+ python -m nexus_agent start # jalankan daemon (blocking)
7
+ python -m nexus_agent status
8
+ python -m nexus_agent reset # lupakan enrollment
9
+ python -m nexus_agent collect # sekali jalan: cetak telemetri (tanpa kirim)
10
+
11
+ Env:
12
+ NEXUS_AGENT_DB path file state SQLite (default ./fleet_agent.db)
13
+ """
14
+ import argparse
15
+ import json
16
+ import os
17
+ import sys
18
+
19
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
20
+
21
+ from nexus_agent import agent # noqa: E402
22
+ from nexus_common import protocol as fc # noqa: E402
23
+
24
+
25
+ def main(argv=None):
26
+ p = argparse.ArgumentParser(prog="nexus-agent",
27
+ description="Daemon endpoint Nexus Fleet (ala-Wazuh agent).")
28
+ sub = p.add_subparsers(dest="action", required=True)
29
+ e = sub.add_parser("enroll", help="daftar ke manager")
30
+ e.add_argument("--host", required=True)
31
+ e.add_argument("--port", default=str(fc.DEFAULT_MANAGER_PORT))
32
+ e.add_argument("--key", required=True, help="enrollment key dari manager")
33
+ e.add_argument("--name", default="")
34
+ e.add_argument("--labels", default="", help="label/grup, dipisah koma (mis. prod,web)")
35
+ sub.add_parser("start", help="jalankan daemon (blocking)")
36
+ sub.add_parser("status")
37
+ sub.add_parser("reset")
38
+ sub.add_parser("collect", help="kumpulkan telemetri sekali & cetak")
39
+ args = p.parse_args(argv)
40
+
41
+ if args.action == "enroll":
42
+ print(json.dumps(agent.enroll(args.host, args.port, args.key, args.name,
43
+ args.labels), indent=2))
44
+ return 0
45
+ if args.action == "start":
46
+ agent.run_foreground()
47
+ return 0
48
+ if args.action == "status":
49
+ print(json.dumps(agent.status(), indent=2))
50
+ return 0
51
+ if args.action == "reset":
52
+ print(json.dumps(agent.reset(), indent=2))
53
+ return 0
54
+ if args.action == "collect":
55
+ print(json.dumps(agent.collect_all(), indent=2, ensure_ascii=False))
56
+ return 0
57
+ return 1
58
+
59
+
60
+ if __name__ == "__main__":
61
+ raise SystemExit(main())
@@ -0,0 +1,351 @@
1
+ # nexus_agent/agent.py
2
+ """
3
+ Daemon endpoint Nexus Fleet: enroll, heartbeat, kumpulkan telemetri, kirim ke
4
+ manager dgn antrian store-and-forward, terapkan policy & perintah.
5
+ """
6
+ import hashlib
7
+ import json
8
+ import os
9
+ import sqlite3
10
+ import time
11
+
12
+ from nexus_common import protocol as fc
13
+ from nexus_common.log import log
14
+ from nexus_agent import collectors
15
+
16
+ _RUN = True
17
+ _SEV_RANK = {s: i for i, s in enumerate(fc.SEVERITIES)}
18
+
19
+
20
+ # --------------------------------------------------------------------------- state
21
+ def _conn():
22
+ c = sqlite3.connect(fc.agent_state_path(), timeout=10)
23
+ c.row_factory = sqlite3.Row
24
+ return c
25
+
26
+
27
+ def _init():
28
+ c = _conn()
29
+ c.executescript(
30
+ """
31
+ CREATE TABLE IF NOT EXISTS state (key TEXT PRIMARY KEY, value TEXT);
32
+ CREATE TABLE IF NOT EXISTS queue (
33
+ id INTEGER PRIMARY KEY AUTOINCREMENT, ts INTEGER, payload TEXT
34
+ );
35
+ """
36
+ )
37
+ # Migrasi dari skema lama (kolom terpisah) -> simpan event utuh sebagai JSON,
38
+ # agar field kaya (event_type/target/evidence/origin) tidak hilang saat dikirim.
39
+ cols = {r[1] for r in c.execute("PRAGMA table_info(queue)").fetchall()}
40
+ if "payload" not in cols:
41
+ c.execute("DROP TABLE IF EXISTS queue")
42
+ c.execute("CREATE TABLE queue (id INTEGER PRIMARY KEY AUTOINCREMENT, "
43
+ "ts INTEGER, payload TEXT)")
44
+ c.commit(); c.close()
45
+
46
+
47
+ def _get(key, default=None):
48
+ c = _conn()
49
+ row = c.execute("SELECT value FROM state WHERE key=?", (key,)).fetchone()
50
+ c.close()
51
+ return row["value"] if row else default
52
+
53
+
54
+ def _set(key, value):
55
+ c = _conn()
56
+ c.execute("INSERT INTO state(key,value) VALUES(?,?) "
57
+ "ON CONFLICT(key) DO UPDATE SET value=excluded.value", (key, str(value)))
58
+ c.commit(); c.close()
59
+
60
+
61
+ def _policy():
62
+ try:
63
+ return json.loads(_get("policy") or "{}") or {}
64
+ except Exception:
65
+ return {}
66
+
67
+
68
+ def _enqueue(events):
69
+ if not events:
70
+ return
71
+ c = _conn()
72
+ for e in events:
73
+ # Simpan event UTUH (semua field schema) sebagai JSON.
74
+ c.execute("INSERT INTO queue(ts,payload) VALUES(?,?)",
75
+ (int(e.get("ts", fc.now())), json.dumps(e)))
76
+ c.commit(); c.close()
77
+
78
+
79
+ def _queue_size():
80
+ c = _conn()
81
+ n = c.execute("SELECT COUNT(*) n FROM queue").fetchone()["n"]
82
+ c.close()
83
+ return n
84
+
85
+
86
+ # --------------------------------------------------------------------------- collect
87
+ def collect_all():
88
+ pol = _policy()
89
+ enabled = pol.get("collectors", collectors.NAMES)
90
+ min_sev = _SEV_RANK.get(pol.get("min_report_severity", "info"), 0)
91
+ out = []
92
+ for name in enabled:
93
+ try:
94
+ events = _fim_collect(pol) if name == "fim" else None
95
+ if events is None:
96
+ fn = collectors.REGISTRY.get(name)
97
+ if not fn:
98
+ continue
99
+ events = fn(pol)
100
+ for e in events:
101
+ if _SEV_RANK.get(e.get("severity", "info"), 0) >= min_sev:
102
+ e.setdefault("ts", fc.now())
103
+ e.setdefault("source", e.get("type", name))
104
+ e["origin"] = "real" # item #4: telemetri agent SELALU real
105
+ out.append(e)
106
+ except Exception as ex:
107
+ log(f"[AGENT] collector {name} error: {ex}")
108
+ return out
109
+
110
+
111
+ # --------------------------------------------------------------------------- FIM (item #1)
112
+ def _sha256(path):
113
+ h = hashlib.sha256()
114
+ with open(path, "rb") as f:
115
+ for chunk in iter(lambda: f.read(65536), b""):
116
+ h.update(chunk)
117
+ return h.hexdigest()
118
+
119
+
120
+ def _fim_files(paths):
121
+ files = []
122
+ for p in paths:
123
+ if os.path.isfile(p):
124
+ files.append(p)
125
+ elif os.path.isdir(p):
126
+ for root, _dirs, names in os.walk(p):
127
+ for n in names:
128
+ files.append(os.path.join(root, n))
129
+ if len(files) > 2000: # batasi beban
130
+ return files
131
+ return files
132
+
133
+
134
+ def _fim_collect(pol):
135
+ """File Integrity Monitoring: baseline checksum -> alert saat dibuat/diubah/dihapus."""
136
+ paths = pol.get("fim_paths", []) or []
137
+ if not paths:
138
+ return []
139
+ try:
140
+ baseline = json.loads(_get("fim_baseline") or "{}")
141
+ except Exception:
142
+ baseline = {}
143
+ had_baseline = bool(baseline)
144
+ current, events = {}, []
145
+ for f in _fim_files(paths):
146
+ try:
147
+ current[f] = _sha256(f)
148
+ except Exception:
149
+ continue
150
+ for f, h in current.items():
151
+ old = baseline.get(f)
152
+ if old is None:
153
+ if had_baseline: # baru (hanya alert bila baseline sudah ada -> hindari noise run pertama)
154
+ events.append({"type": "fim_new", "severity": "medium", "event_type": "file_created",
155
+ "title": f"File baru dipantau: {os.path.basename(f)}", "detail": f,
156
+ "target": {"path": f}, "evidence": {"new_hash": h}})
157
+ elif old != h:
158
+ events.append({"type": "fim_change", "severity": "high", "event_type": "file_modified",
159
+ "title": f"File diubah: {os.path.basename(f)}", "detail": f,
160
+ "target": {"path": f}, "evidence": {"old_hash": old, "new_hash": h}})
161
+ for f, old in baseline.items():
162
+ if f not in current:
163
+ events.append({"type": "fim_deleted", "severity": "medium", "event_type": "file_deleted",
164
+ "title": f"File dihapus: {os.path.basename(f)}", "detail": f,
165
+ "target": {"path": f}, "evidence": {"old_hash": old}})
166
+ _set("fim_baseline", json.dumps(current))
167
+ return events
168
+
169
+
170
+ # --------------------------------------------------------------------------- networking
171
+ def _murl(path):
172
+ return fc.manager_url(_get("manager_host", fc.DEFAULT_MANAGER_HOST),
173
+ _get("manager_port", fc.DEFAULT_MANAGER_PORT), path)
174
+
175
+
176
+ def enroll(manager_host, manager_port, enroll_key, name="", labels=None):
177
+ _init()
178
+ fp = fc.host_fingerprint()
179
+ if isinstance(labels, str):
180
+ labels = [x.strip() for x in labels.split(",") if x.strip()]
181
+ body = {"name": name or fp["hostname"], "fingerprint": fp, "ip": fc.local_ip(),
182
+ "labels": labels or []}
183
+ try:
184
+ resp = fc.post_enroll(fc.manager_url(manager_host, manager_port, "/enroll"),
185
+ body, enroll_key)
186
+ except fc.HttpError as e:
187
+ return {"module": "fleet_agent", "ok": False, "error": f"{e.status}: {e.body}"}
188
+ except Exception as e:
189
+ return {"module": "fleet_agent", "ok": False, "error": str(e)}
190
+ _set("manager_host", manager_host)
191
+ _set("manager_port", str(manager_port))
192
+ _set("agent_id", resp["agent_id"])
193
+ _set("agent_key", resp["agent_key"])
194
+ _set("name", body["name"])
195
+ _set("policy", json.dumps(resp.get("policy", {})))
196
+ _set("policy_version", str(resp.get("policy_version", 1)))
197
+ log(f"[AGENT] Terdaftar sebagai {resp['agent_id']} di {manager_host}:{manager_port}")
198
+ return {"module": "fleet_agent", "ok": True, "agent_id": resp["agent_id"],
199
+ "manager": f"{manager_host}:{manager_port}"}
200
+
201
+
202
+ def _flush_queue():
203
+ aid, akey = _get("agent_id"), _get("agent_key")
204
+ if not aid:
205
+ return 0
206
+ c = _conn()
207
+ rows = c.execute("SELECT * FROM queue ORDER BY id ASC LIMIT 200").fetchall()
208
+ c.close()
209
+ if not rows:
210
+ return 0
211
+ events = []
212
+ for r in rows:
213
+ try:
214
+ events.append(json.loads(r["payload"]))
215
+ except Exception:
216
+ continue
217
+ try:
218
+ fc.post_signed(_murl("/events"), {"events": events}, aid, akey)
219
+ except Exception as e:
220
+ log(f"[AGENT] Manager tak terjangkau, {len(events)} event tetap diantri ({e})")
221
+ return 0
222
+ ids = [r["id"] for r in rows]
223
+ c = _conn()
224
+ c.execute(f"DELETE FROM queue WHERE id IN ({','.join('?' * len(ids))})", ids)
225
+ c.commit(); c.close()
226
+ return len(ids)
227
+
228
+
229
+ def _active_response(args):
230
+ """Active Response (item Active Response). DEFAULT DRY-RUN untuk keamanan/etika:
231
+ hanya jalankan blokir nyata bila policy `active_response` diaktifkan."""
232
+ import subprocess
233
+ action = args.get("action", "")
234
+ ip = args.get("ip", "") or args.get("target", "")
235
+ enabled = str(_policy().get("active_response", "")).lower() in ("1", "true", "yes")
236
+ executed = False
237
+ if action == "block_ip" and ip:
238
+ if enabled:
239
+ try:
240
+ if __import__("platform").system() == "Windows":
241
+ cmd = ["netsh", "advfirewall", "firewall", "add", "rule",
242
+ f"name=NexusBlock-{ip}", "dir=in", "action=block", f"remoteip={ip}"]
243
+ else:
244
+ cmd = ["iptables", "-A", "INPUT", "-s", ip, "-j", "DROP"]
245
+ subprocess.run(cmd, capture_output=True, timeout=10)
246
+ executed = True
247
+ log(f"[AGENT] Active Response: IP {ip} DIBLOKIR di firewall.")
248
+ except Exception as e:
249
+ log(f"[AGENT] Active Response gagal: {e}")
250
+ else:
251
+ log(f"[AGENT] (DRY-RUN) block_ip {ip} — aktifkan policy.active_response utk eksekusi.")
252
+ else:
253
+ log(f"[AGENT] Active Response: aksi '{action}' tidak dikenal/lengkap.")
254
+ _enqueue([{"type": "response", "severity": "medium", "event_type": "active_response",
255
+ "title": f"Active Response: {action} {ip} ({'executed' if executed else 'dry-run'})",
256
+ "detail": f"action={action} ip={ip} executed={executed}",
257
+ "target": {"ip": ip}, "data": {"action": action, "executed": executed},
258
+ "origin": "real", "source": "response"}])
259
+
260
+
261
+ def _handle_commands(cmds):
262
+ for cmd in cmds:
263
+ name = cmd.get("command")
264
+ if name == "collect_now":
265
+ _enqueue(collect_all())
266
+ log("[AGENT] Perintah collect_now dijalankan.")
267
+ elif name == "respond":
268
+ _active_response(cmd.get("args") or {})
269
+ elif name == "ping":
270
+ log("[AGENT] Perintah ping diterima dari manager.")
271
+ elif name == "set_name":
272
+ nm = (cmd.get("args") or {}).get("name")
273
+ if nm:
274
+ _set("name", nm)
275
+ log(f"[AGENT] Nama agent diubah -> {nm}")
276
+
277
+
278
+ def _heartbeat():
279
+ aid, akey = _get("agent_id"), _get("agent_key")
280
+ try:
281
+ resp = fc.post_signed(_murl("/heartbeat"), {"ip": fc.local_ip()}, aid, akey)
282
+ except Exception as e:
283
+ log(f"[AGENT] Heartbeat gagal (manager offline?): {e}")
284
+ return
285
+ if resp.get("policy_version", 0) != int(_get("policy_version", "0") or 0):
286
+ try:
287
+ pol = fc.get_admin(_murl("/policy"))
288
+ _set("policy", json.dumps(pol.get("policy", {})))
289
+ _set("policy_version", str(pol.get("policy_version", 1)))
290
+ log(f"[AGENT] Policy diperbarui -> versi {pol.get('policy_version')}")
291
+ except Exception:
292
+ pass
293
+ _handle_commands(resp.get("commands", []))
294
+
295
+
296
+ def run_foreground(**kwargs):
297
+ global _RUN
298
+ _init()
299
+ if not _get("agent_id"):
300
+ log("[AGENT] Belum ter-enroll. Jalankan enrollment dulu.")
301
+ return {"module": "fleet_agent", "status": "error", "error": "not_enrolled"}
302
+ _RUN = True
303
+ log(f"[AGENT] Daemon mulai: {_get('agent_id')} -> "
304
+ f"{_get('manager_host')}:{_get('manager_port')}")
305
+ last_hb = last_collect = 0
306
+ _enqueue(collect_all())
307
+ try:
308
+ while _RUN:
309
+ pol = _policy()
310
+ hb_iv = int(pol.get("heartbeat_interval", fc.HEARTBEAT_INTERVAL))
311
+ col_iv = int(pol.get("collect_interval", fc.COLLECT_INTERVAL))
312
+ t = fc.now()
313
+ if t - last_hb >= hb_iv:
314
+ _heartbeat(); last_hb = t
315
+ if t - last_collect >= col_iv:
316
+ evts = collect_all(); _enqueue(evts)
317
+ log(f"[AGENT] Telemetri terkumpul: {len(evts)} event (antrian={_queue_size()})")
318
+ last_collect = t
319
+ sent = _flush_queue()
320
+ if sent:
321
+ log(f"[AGENT] {sent} event terkirim ke manager.")
322
+ time.sleep(2)
323
+ except KeyboardInterrupt:
324
+ pass
325
+ log("[AGENT] Daemon berhenti.")
326
+ return {"module": "fleet_agent", "status": "stopped"}
327
+
328
+
329
+ def stop():
330
+ global _RUN
331
+ _RUN = False
332
+ return {"module": "fleet_agent", "status": "stopped"}
333
+
334
+
335
+ def status():
336
+ _init()
337
+ return {"module": "fleet_agent", "enrolled": bool(_get("agent_id")),
338
+ "agent_id": _get("agent_id", ""), "name": _get("name", ""),
339
+ "manager_host": _get("manager_host", ""), "manager_port": _get("manager_port", ""),
340
+ "policy_version": int(_get("policy_version", "0") or 0),
341
+ "queue_size": _queue_size(), "collectors": collectors.NAMES}
342
+
343
+
344
+ def reset():
345
+ import os
346
+ try:
347
+ if os.path.exists(fc.agent_state_path()):
348
+ os.remove(fc.agent_state_path())
349
+ except Exception as e:
350
+ return {"module": "fleet_agent", "ok": False, "error": str(e)}
351
+ return {"module": "fleet_agent", "ok": True}