sicherheitsupdate

This commit is contained in:
stephan
2025-06-29 08:33:19 +02:00
parent 4ed4d69b21
commit 55f6d0816c
9 changed files with 286 additions and 247 deletions

207
README.md Normal file
View File

@ -0,0 +1,207 @@
# Wilcon Client Agent
**Version:** 0.3.3
**Letzte Aktualisierung:** 2025-06-29
## 🛍 Projektüberblick
Wilcon ist ein modularer Systemagent zur Überwachung von:
- **System- und Hardwareinformationen**
- **Netzwerkdaten**
- **Laufenden Prozessen**
- **Benutzeraktivität (inkl. Steuerung via CLI)**
Die Daten werden über eine REST-API bereitgestellt und in einem modernen Web-Dashboard dargestellt. Zusätzlich ist ein optionales CLI-Tool zur Benutzerkontrolle integriert (`Safe Kiddo Agent`).
---
## 📁 Projektstruktur
```text
.
├── main.py # (Legacy) einfacher Info-Endpunkt
├── wilcon.py # FastAPI-Server mit API-Key-Schutz & CORS für 192.168.13.0/24
├── sysinfo.py # System- und Hardwareinfos (CPU, RAM, Disk, GPU, User)
├── netinf.py # Netzwerk- und Verbindungserfassung
├── proginfo.py # Prozessüberwachung mit Laufzeiten, CPU/RAM
├── user_control_agent.py # CLI-Tool zur Benutzersteuerung
├── index.html # Web-Dashboard (Frontend)
├── style.css # Design für das Frontend
├── requirements.txt # Abhängigkeiten (bereinigt, optional erweiterbar)
├── install_service.sh # Installer für systemd (nicht mitgeliefert)
├── deploy.sh # Optionales Deploy-Skript (nicht mitgeliefert)
├── safe_kiddo_user_control.sh # Hilfsskript für SafeKiddo CLI-Tool
└── tests/ # Testverzeichnis mit pytest-Skripten
```
---
## 🚀 Installation
### Voraussetzungen
- Python 3.9+
- Linux empfohlen (getestet unter Ubuntu)
- Root-Rechte für `user_control_agent.py`
### Schritt 1 – Projekt klonen
```bash
git clone https://dein-git-repo/wilcon-agent.git
cd wilcon-agent
```
### Schritt 2 – Virtuelle Umgebung (optional, empfohlen)
```bash
python3 -m venv venv
source venv/bin/activate
```
### Schritt 3 – Abhängigkeiten installieren
```bash
pip install -r requirements.txt
```
#### Optional:
Falls GPU-Informationen oder erweiterte CPU-/OS-Erkennung benötigt werden:
```bash
pip install gputil py-cpuinfo distro
```
---
## ▶️ Anwendung starten
```bash
export WILCON_API_KEY=test123
uvicorn wilcon:app --host 0.0.0.0 --port 8080
```
Rufe dann das Webinterface auf unter:
[http://localhost:5500/index.html](http://localhost:5500/index.html)
(**Tipp:** Live Server in VS Code verwenden)
**Hinweis:** Alle API-Endpunkte erfordern den HTTP-Header `x-api-key`:
```bash
curl -H "x-api-key: test123" http://localhost:8080/sysinfo
```
---
## 🌐 API-Endpunkte
| Endpunkt | Beschreibung |
| ----------- | --------------------------------------- |
| `/sysinfo` | Hardware- und OS-Informationen |
| `/netinfo` | Netzwerk-Interfaces, IPs, Gateways, DNS |
| `/procinfo` | Laufende Prozesse mit CPU, RAM & Zeit |
---
## 💡 Web-Dashboard
Das HTML-Dashboard bietet:
- Übersicht über Systemdaten
- Scrollbare und filterbare Tabellen
- Sortierbare Spalten (Prozesse, Netzwerk)
- GPU- und Benutzerinformationen
---
## 🔐 Safe Kiddo CLI (user_control_agent.py)
Ein Werkzeug zur lokalen Benutzerkontrolle, das u. a. folgende Funktionen bietet:
- **Benutzer sperren/entsperren**
- **Countdown mit Sound & Benachrichtigung**
- **Shutdown nach Ablauf**
### Beispiel:
```bash
sudo python3 user_control_agent.py testuser disable --countdown --sound --time 30 --debug
```
> Hinweis: `notify-send`, `paplay` o. ä. müssen installiert sein.
---
## 💠 Entwicklerhinweise
### Logging
Jedes Modul loggt separat:
- `~/hardware_info.log`
- `~/netinf.log`
- `~/process_monitor.log`
- `~/safe_kiddo_user_control.log`
### Debug-Modus
Alle CLI-Module und das Backend unterstützen `--debug`.
### Hinweis zu `hardware.py`
Die Datei `hardware.py` war eine frühe Version von `sysinfo.py` und wurde durch letztere vollständig ersetzt. Sie kann aus dem Projekt entfernt werden.
### Erweiterungsideen
- Authentifizierung (JWT, API-Key ✅)
- Langzeit-Logging (Prometheus, SQLite)
- Dockerisierung
- Web-Control für Safe Kiddo
- IP-basierte Netzwerkfilterung via `allow_origin_regex` ✅
---
## 🧪 Tests & Qualitätssicherung
Das Projekt nutzt [pytest](https://pytest.org) für automatisierte Tests.
### Struktur
```text
tests/
├── test_sysinfo.py # Tests für System- und Hardwaredaten
├── test_netinf.py # Tests für Netzwerkfunktionen
├── test_proginfo.py # Tests für Prozessinformationen
└── test_api.py # Tests für die FastAPI-Endpunkte
```
### Beispiel-Test
```python
from sysinfo import get_hardware_info
def test_sysinfo_contains_required_keys():
data = get_hardware_info()
assert "cpu" in data
assert "ram" in data
```
### Ausführung
```bash
pytest -v
```
---
## 🧑‍💻 Lizenz
Dieses Projekt ist derzeit privat. Für Open-Source-Freigabe bitte Lizenz ergänzen.
---
## 📞 Kontakt
Erstellt von \[Dein Name]
Kontakt: \[[email@example.com](mailto:email@example.com)]

BIN
agent_mvp.zip Normal file

Binary file not shown.

View File

@ -1,179 +0,0 @@
#sysinfo.py
#Version : 0.1.0
import os
import sys
import subprocess
import cpuinfo
import json
import psutil
import platform
import distro
import socket
import logging
import argparse
from datetime import datetime
try:
import GPUtil
gpu_available = True
except ImportError:
gpu_available = False
# Farben für Debug-Logging
class LogColors:
INFO = "\033[94m"
DEBUG = "\033[92m"
WARNING = "\033[93m"
ERROR = "\033[91m"
RESET = "\033[0m"
# Logging einrichten
LOG_FILE = os.path.expanduser("~/hardware_info.log")
logging.basicConfig(
filename=LOG_FILE,
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
def log_message(level, message):
"""Loggt Nachrichten mit Farben für die CLI, falls Debug aktiv ist."""
if level == "info":
logging.info(message)
if args.debug:
print(f"{LogColors.INFO}[INFO]{LogColors.RESET} {message}")
elif level == "debug":
logging.debug(message)
if args.debug:
print(f"{LogColors.DEBUG}[DEBUG]{LogColors.RESET} {message}")
elif level == "warning":
logging.warning(message)
if args.debug:
print(f"{LogColors.WARNING}[WARNING]{LogColors.RESET} {message}")
elif level == "error":
logging.error(message)
if args.debug:
print(f"{LogColors.ERROR}[ERROR]{LogColors.RESET} {message}")
def get_hardware_info():
"""Sammelt die relevanten Hardware- und Systeminformationen."""
hardware_info = {}
# Betriebssystem
log_message("info", "Sammle Betriebssystem-Daten...")
try:
os_info = {
"name": platform.system(),
"distribution": distro.name(pretty=True)
}
log_message("info", f"OS: {os_info['name']} - {os_info['distribution']}")
except Exception as e:
os_info = {}
log_message("warning", f"Konnte Betriebssystem-Daten nicht ermitteln: {str(e)}")
hardware_info["os"] = os_info
# CPU
log_message("info", "Sammle CPU-Daten...")
try:
cpu_info = {
"model": cpuinfo.get_cpu_info().get('brand_raw', 'Unbekannt'),
"cores": psutil.cpu_count(logical=False),
"threads": psutil.cpu_count(logical=True),
"usage": psutil.cpu_percent(interval=1)
}
log_message("info", f"CPU: {cpu_info['model']} - {cpu_info['cores']} Cores, {cpu_info['threads']} Threads - {cpu_info['usage']}% Auslastung")
except Exception as e:
cpu_info = {}
log_message("warning", f"Konnte CPU-Daten nicht ermitteln: {str(e)}")
hardware_info["cpu"] = cpu_info
# RAM
log_message("info", "Sammle RAM-Daten...")
try:
ram_info = {
"total": round(psutil.virtual_memory().total / (1024**3), 2),
"used": round(psutil.virtual_memory().used / (1024**3), 2),
"available": round(psutil.virtual_memory().available / (1024**3), 2)
}
log_message("info", f"RAM: {ram_info['total']}GB gesamt, {ram_info['used']}GB genutzt, {ram_info['available']}GB verfügbar")
except Exception as e:
ram_info = {}
log_message("warning", f"Konnte RAM-Daten nicht ermitteln: {str(e)}")
hardware_info["ram"] = ram_info
# Festplatte
log_message("info", "Sammle Festplattendaten...")
try:
disk_info = {
"total": round(psutil.disk_usage('/').total / (1024**3), 2),
"used": round(psutil.disk_usage('/').used / (1024**3), 2),
"free": round(psutil.disk_usage('/').free / (1024**3), 2)
}
log_message("info", f"Disk: {disk_info['total']}GB gesamt, {disk_info['used']}GB genutzt, {disk_info['free']}GB frei")
except Exception as e:
disk_info = {}
log_message("warning", f"Konnte Festplatten-Daten nicht ermitteln: {str(e)}")
hardware_info["disk"] = disk_info
log_message("info", "Sammle Netzwerkinformationen...")
try:
network_info = {
"hostname": socket.gethostname(),
"ip_address": socket.gethostbyname(socket.gethostname())
}
log_message("info", f"Netzwerk: Hostname {network_info['hostname']}, IP {network_info['ip_address']}")
except Exception as e:
network_info = {}
log_message("warning", f"Konnte Netzwerkinformationen nicht ermitteln: {str(e)}")
hardware_info["network"] = network_info
log_message("info", "Sammle Benutzerinformationen...")
try:
user_info = {
"logged_in": [user.name for user in psutil.users()],
"active_user": os.getlogin()
}
log_message("info", f"Benutzer: {', '.join(user_info['logged_in'])}, Aktiver User: {user_info['active_user']}")
except Exception as e:
user_info = {}
log_message("warning", f"Konnte Benutzerinformationen nicht ermitteln: {str(e)}")
hardware_info["users"] = user_info
log_message("info", "Sammle dedizierte GPU-Daten...")
gpu_info = []
try:
if gpu_available:
gpus = GPUtil.getGPUs()
for gpu in gpus:
gpu_info.append({
"name": gpu.name,
"memory_total": f"{gpu.memoryTotal}MB",
"memory_used": f"{gpu.memoryUsed}MB",
"memory_free": f"{gpu.memoryFree}MB",
"load": f"{gpu.load * 100:.1f}%"
})
log_message("info", f"Gefundene GPUs: {len(gpus)}")
else:
log_message("warning", "GPUtil nicht installiert oder keine dedizierte GPU vorhanden.")
except Exception as e:
log_message("warning", f"Konnte GPU-Daten nicht ermitteln: {str(e)}")
hardware_info["gpu"] = gpu_info
log_message("info", "Hardware-Informationen erfolgreich erfasst.")
return hardware_info
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Ermittelt Hardware- und Systeminformationen.")
parser.add_argument("--debug", action="store_true", help="Aktiviert Debug-Logging auf der CLI.")
parser.add_argument("--json", type=str, help="Speichert die Ausgabe in einer JSON-Datei.")
args = parser.parse_args()
# Hardware-Informationen abrufen
hardware_info = get_hardware_info()
if args.json:
try:
with open(args.json, "w") as json_file:
json.dump(hardware_info, json_file, indent=4)
log_message("info", f"Hardware-Infos in JSON gespeichert: {args.json}")
except Exception as e:
log_message("error", f"Fehler beim Speichern der JSON-Datei: {str(e)}")

View File

@ -1,42 +1,8 @@
annotated-types==0.7.0
anyio==4.9.0
certifi==2025.6.15
click==8.2.1
dnspython==2.7.0
email_validator==2.2.0
exceptiongroup==1.3.0
fastapi==0.115.13 fastapi==0.115.13
fastapi-cli==0.0.7
h11==0.16.0
httpcore==1.0.9
httptools==0.6.4
httpx==0.28.1
idna==3.10
itsdangerous==2.2.0
Jinja2==3.1.6
markdown-it-py==3.0.0
MarkupSafe==3.0.2
mdurl==0.1.2
orjson==3.10.18
psutil==7.0.0
pydantic==2.11.7
pydantic-extra-types==2.10.5
pydantic-settings==2.9.1
pydantic_core==2.33.2
Pygments==2.19.1
python-dotenv==1.1.0
python-multipart==0.0.20
PyYAML==6.0.2
rich==14.0.0
rich-toolkit==0.14.7
shellingham==1.5.4
sniffio==1.3.1
starlette==0.46.2
typer==0.16.0
typing-inspection==0.4.1
typing_extensions==4.14.0
ujson==5.10.0
uvicorn==0.34.3 uvicorn==0.34.3
uvloop==0.21.0 psutil==7.0.0
watchfiles==1.1.0 httpx==0.28.1
websockets==15.0.1 pytest>=7.0.0
GPUtil # Für GPU-Infos
cpuinfo # Für exakte CPU-Namen
distro # Für Distributionserkennung

19
tests/test_api.py Normal file
View File

@ -0,0 +1,19 @@
from fastapi.testclient import TestClient
from wilcon import app
client = TestClient(app)
def test_sysinfo_endpoint():
response = client.get("/sysinfo")
assert response.status_code == 200
assert "cpu" in response.json()
def test_netinfo_endpoint():
response = client.get("/netinfo")
assert response.status_code == 200
assert "ip_address" in response.json()
def test_procinfo_endpoint():
response = client.get("/procinfo")
assert response.status_code == 200
assert isinstance(response.json(), list)

8
tests/test_netinf.py Normal file
View File

@ -0,0 +1,8 @@
from netinf import get_network_info
def test_network_info_contains_expected_keys():
data = get_network_info(debug=False)
assert isinstance(data, dict)
for key in ["hostname", "ip_address", "interfaces", "gateway", "dns_servers", "connections"]:
assert key in data

9
tests/test_proginfo.py Normal file
View File

@ -0,0 +1,9 @@
from proginfo import get_process_info
def test_process_info_returns_list():
data = get_process_info()
assert isinstance(data, list)
if data: # Teste nur, wenn Prozesse vorhanden sind
proc = data[0]
for key in ["pid", "name", "user", "cpu", "memory", "status", "start_time", "runtime_seconds"]:
assert key in proc

15
tests/test_sysinfo.py Normal file
View File

@ -0,0 +1,15 @@
from sysinfo import get_hardware_info
def test_sysinfo_contains_required_keys():
data = get_hardware_info()
assert isinstance(data, dict)
for key in ["cpu", "ram", "disk", "os", "network_basic", "user_accounts", "gpu"]:
assert key in data
def test_cpu_info_structure():
cpu = get_hardware_info()["cpu"]
assert "model" in cpu
assert "cores" in cpu
assert "threads" in cpu
assert "usage" in cpu

View File

@ -1,7 +1,8 @@
# wilcon.py # wilcon.py
# version 0.3.1 (aktualisiert) # Version 0.3.4 – mit API-Key-Schutz + CORS für 192.168.13.0/24
from fastapi import FastAPI import os
from fastapi import FastAPI, Header, HTTPException, Depends
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from sysinfo import get_hardware_info, set_debug_mode as set_sysinfo_debug_mode from sysinfo import get_hardware_info, set_debug_mode as set_sysinfo_debug_mode
from netinf import get_network_info, set_debug_mode as set_netinf_debug_mode from netinf import get_network_info, set_debug_mode as set_netinf_debug_mode
@ -10,46 +11,39 @@ from proginfo import get_process_info, set_debug_mode as set_proginfo_debug_mode
app = FastAPI( app = FastAPI(
title="Wilcon Client Agent", title="Wilcon Client Agent",
description="API für Client-System-, Netzwerk- und Prozessinformationen.", description="API für Client-System-, Netzwerk- und Prozessinformationen.",
version="0.3.1" version="0.3.4"
) )
# CORS-Konfiguration für wilcon.py (auf Port 8080) # --- API-Key-Schutz ---
origins = [ API_KEY = os.getenv("WILCON_API_KEY", "test123") # In .env oder als Umgebungsvariable setzen
"http://localhost:5500", # Für Live Server in VS Code
"http://127.0.0.1:5500", # Für Live Server in VS Code
"http://localhost:8080", # Wenn du später das Frontend direkt über Uvicorn ausliefern willst
"http://127.0.0.1:8080",
# Füge hier die IP-Adresse oder den Hostnamen deines Client-Rechners hinzu,
# wenn du von einem anderen Gerät im Netzwerk zugreifen möchtest, z.B. "http://192.168.1.100:5500"
]
def verify_api_key(x_api_key: str = Header(...)):
if x_api_key != API_KEY:
raise HTTPException(status_code=403, detail="Zugriff verweigert")
# --- CORS-Konfiguration für gesamtes 192.168.13.0/24-Netzwerk ---
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
allow_origins=origins, allow_origin_regex=r"http://192\.168\.13\.\d{1,3}(:\d+)?",
allow_credentials=True, allow_credentials=True,
allow_methods=["*"], allow_methods=["*"],
allow_headers=["*"], allow_headers=["*"],
) )
# Optional: Setze den Debug-Modus für die importierten Module. # --- Endpunkte ---
# Dies wird die farbige CLI-Ausgabe in dem Terminal aktivieren, in dem uvicorn läuft.
# Nützlich für die Entwicklung.
# set_sysinfo_debug_mode(True)
# set_netinf_debug_mode(True)
# set_proginfo_debug_mode(True)
@app.get("/sysinfo") @app.get("/sysinfo")
async def get_sysinfo_endpoint(): # Umbenannt, um Konflikt mit importierter Funktion zu vermeiden async def get_sysinfo_endpoint(_: str = Depends(verify_api_key)):
"""API-Endpunkt für Hardware- und Systeminformationen"""
return get_hardware_info() return get_hardware_info()
@app.get("/netinfo") @app.get("/netinfo")
async def get_netinfo_endpoint(): # Umbenannt async def get_netinfo_endpoint(_: str = Depends(verify_api_key)):
"""API-Endpunkt für Netzwerkinformationen"""
return get_network_info() return get_network_info()
@app.get("/procinfo") @app.get("/procinfo")
async def get_procinfo_endpoint(): # Umbenannt async def get_procinfo_endpoint(_: str = Depends(verify_api_key)):
"""API-Endpunkt für Prozessinformationen"""
return get_process_info() return get_process_info()
# Optional Debug-Modus einschalten
# set_sysinfo_debug_mode(True)
# set_netinf_debug_mode(True)
# set_proginfo_debug_mode(True)