Итерация 2: сканирование сети
- Phinx миграции: network_ranges, scan_jobs, discovered_hosts, audit_log - Models: NetworkRange, ScanJob, DiscoveredHost - Repositories с CRUD для всех сущностей - NetworkScanner: ping sweep + TCP connect + ARP table + reverse DNS - PingScanner, TcpPortScanner (curl_multi для параллелизма), ArpTableReader - HostFingerprintService: vendor detection + confidence scoring - ScanJobRunner + CLI worker (bin/run-scan-worker.php) - Discovery UI: управление диапазонами, запуск скана, таблица находок - NetworkRangeController с валидацией CIDR - Обновлён public/index.php с DI конфигурацией для новых сервисов
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domovoy\Repositories;
|
||||
|
||||
use PDO;
|
||||
|
||||
class AuditLogRepository
|
||||
{
|
||||
private PDO $pdo;
|
||||
|
||||
public function __construct(PDO $pdo)
|
||||
{
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
public function log(string $action, ?string $entityType = null, ?string $entityId = null, ?array $details = null, ?string $userId = null): void
|
||||
{
|
||||
$stmt = $this->pdo->prepare(
|
||||
'INSERT INTO audit_log (user_id, action, entity_type, entity_id, details_json, created_at)
|
||||
VALUES (:user_id, :action, :entity_type, :entity_id, :details_json, :created_at)'
|
||||
);
|
||||
$stmt->execute([
|
||||
'user_id' => $userId,
|
||||
'action' => $action,
|
||||
'entity_type' => $entityType,
|
||||
'entity_id' => $entityId,
|
||||
'details_json' => $details !== null ? json_encode($details) : null,
|
||||
'created_at' => (new \DateTimeImmutable())->format('Y-m-d H:i:s'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function findRecent(int $limit = 20): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT * FROM audit_log ORDER BY created_at DESC LIMIT :limit');
|
||||
$stmt->bindValue('limit', $limit, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domovoy\Repositories;
|
||||
|
||||
use Domovoy\Models\DiscoveredHost;
|
||||
use PDO;
|
||||
|
||||
class DiscoveredHostRepository
|
||||
{
|
||||
private PDO $pdo;
|
||||
|
||||
public function __construct(PDO $pdo)
|
||||
{
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
public function findById(int $id): ?DiscoveredHost
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT * FROM discovered_hosts WHERE id = :id');
|
||||
$stmt->execute(['id' => $id]);
|
||||
$row = $stmt->fetch();
|
||||
return $row ? DiscoveredHost::fromArray($row) : null;
|
||||
}
|
||||
|
||||
public function findByStatus(string $status, int $limit = 100): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT * FROM discovered_hosts WHERE status = :status ORDER BY last_seen DESC LIMIT :limit');
|
||||
$stmt->bindValue('status', $status);
|
||||
$stmt->bindValue('limit', $limit, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
$results = [];
|
||||
while ($row = $stmt->fetch()) {
|
||||
$results[] = DiscoveredHost::fromArray($row);
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function findByScanJob(int $scanJobId): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT * FROM discovered_hosts WHERE scan_job_id = :scan_job_id ORDER BY ip_address ASC');
|
||||
$stmt->execute(['scan_job_id' => $scanJobId]);
|
||||
$results = [];
|
||||
while ($row = $stmt->fetch()) {
|
||||
$results[] = DiscoveredHost::fromArray($row);
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function findAll(int $limit = 100): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT * FROM discovered_hosts ORDER BY last_seen DESC LIMIT :limit');
|
||||
$stmt->bindValue('limit', $limit, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
$results = [];
|
||||
while ($row = $stmt->fetch()) {
|
||||
$results[] = DiscoveredHost::fromArray($row);
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function save(DiscoveredHost $host): void
|
||||
{
|
||||
$now = (new \DateTimeImmutable())->format('Y-m-d H:i:s');
|
||||
if ($host->id === null) {
|
||||
$stmt = $this->pdo->prepare(
|
||||
'INSERT INTO discovered_hosts
|
||||
(scan_job_id, ip_address, mac_address, hostname, vendor, detected_os,
|
||||
open_ports_json, protocols_json, fingerprint_json, confidence, status,
|
||||
matched_device_id, first_seen, last_seen, created_at, updated_at)
|
||||
VALUES
|
||||
(:scan_job_id, :ip_address, :mac_address, :hostname, :vendor, :detected_os,
|
||||
:open_ports_json, :protocols_json, :fingerprint_json, :confidence, :status,
|
||||
:matched_device_id, :first_seen, :last_seen, :created_at, :updated_at)'
|
||||
);
|
||||
$stmt->execute([
|
||||
'scan_job_id' => $host->scanJobId,
|
||||
'ip_address' => $host->ipAddress,
|
||||
'mac_address' => $host->macAddress,
|
||||
'hostname' => $host->hostname,
|
||||
'vendor' => $host->vendor,
|
||||
'detected_os' => $host->detectedOs,
|
||||
'open_ports_json' => json_encode($host->openPorts),
|
||||
'protocols_json' => json_encode($host->protocols),
|
||||
'fingerprint_json' => json_encode($host->fingerprint),
|
||||
'confidence' => $host->confidence,
|
||||
'status' => $host->status,
|
||||
'matched_device_id' => $host->matchedDeviceId,
|
||||
'first_seen' => $now,
|
||||
'last_seen' => $now,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
$host->id = (int)$this->pdo->lastInsertId();
|
||||
} else {
|
||||
$stmt = $this->pdo->prepare(
|
||||
'UPDATE discovered_hosts SET
|
||||
status = :status, matched_device_id = :matched_device_id,
|
||||
last_seen = :last_seen, updated_at = :updated_at
|
||||
WHERE id = :id'
|
||||
);
|
||||
$stmt->execute([
|
||||
'id' => $host->id,
|
||||
'status' => $host->status,
|
||||
'matched_device_id' => $host->matchedDeviceId,
|
||||
'last_seen' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domovoy\Repositories;
|
||||
|
||||
use Domovoy\Models\NetworkRange;
|
||||
use PDO;
|
||||
|
||||
class NetworkRangeRepository
|
||||
{
|
||||
private PDO $pdo;
|
||||
|
||||
public function __construct(PDO $pdo)
|
||||
{
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
public function findById(int $id): ?NetworkRange
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT * FROM network_ranges WHERE id = :id');
|
||||
$stmt->execute(['id' => $id]);
|
||||
$row = $stmt->fetch();
|
||||
return $row ? NetworkRange::fromArray($row) : null;
|
||||
}
|
||||
|
||||
public function findAll(): array
|
||||
{
|
||||
$stmt = $this->pdo->query('SELECT * FROM network_ranges ORDER BY name ASC');
|
||||
$results = [];
|
||||
while ($row = $stmt->fetch()) {
|
||||
$results[] = NetworkRange::fromArray($row);
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function findEnabled(): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT * FROM network_ranges WHERE enabled = 1 ORDER BY name ASC');
|
||||
$stmt->execute();
|
||||
$results = [];
|
||||
while ($row = $stmt->fetch()) {
|
||||
$results[] = NetworkRange::fromArray($row);
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function save(NetworkRange $range): void
|
||||
{
|
||||
$now = (new \DateTimeImmutable())->format('Y-m-d H:i:s');
|
||||
if ($range->id === null) {
|
||||
$stmt = $this->pdo->prepare(
|
||||
'INSERT INTO network_ranges (name, cidr, enabled, created_at, updated_at)
|
||||
VALUES (:name, :cidr, :enabled, :created_at, :updated_at)'
|
||||
);
|
||||
$stmt->execute([
|
||||
'name' => $range->name,
|
||||
'cidr' => $range->cidr,
|
||||
'enabled' => $range->enabled ? 1 : 0,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
$range->id = (int)$this->pdo->lastInsertId();
|
||||
} else {
|
||||
$stmt = $this->pdo->prepare(
|
||||
'UPDATE network_ranges SET name = :name, cidr = :cidr, enabled = :enabled, updated_at = :updated_at WHERE id = :id'
|
||||
);
|
||||
$stmt->execute([
|
||||
'id' => $range->id,
|
||||
'name' => $range->name,
|
||||
'cidr' => $range->cidr,
|
||||
'enabled' => $range->enabled ? 1 : 0,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete(int $id): void
|
||||
{
|
||||
$stmt = $this->pdo->prepare('DELETE FROM network_ranges WHERE id = :id');
|
||||
$stmt->execute(['id' => $id]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domovoy\Repositories;
|
||||
|
||||
use Domovoy\Models\ScanJob;
|
||||
use PDO;
|
||||
|
||||
class ScanJobRepository
|
||||
{
|
||||
private PDO $pdo;
|
||||
|
||||
public function __construct(PDO $pdo)
|
||||
{
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
public function findById(int $id): ?ScanJob
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT * FROM scan_jobs WHERE id = :id');
|
||||
$stmt->execute(['id' => $id]);
|
||||
$row = $stmt->fetch();
|
||||
return $row ? ScanJob::fromArray($row) : null;
|
||||
}
|
||||
|
||||
public function findPending(): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT * FROM scan_jobs WHERE status = :status ORDER BY created_at ASC');
|
||||
$stmt->execute(['status' => 'pending']);
|
||||
$results = [];
|
||||
while ($row = $stmt->fetch()) {
|
||||
$results[] = ScanJob::fromArray($row);
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function findRecent(int $limit = 10): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT * FROM scan_jobs ORDER BY created_at DESC LIMIT :limit');
|
||||
$stmt->bindValue('limit', $limit, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
$results = [];
|
||||
while ($row = $stmt->fetch()) {
|
||||
$results[] = ScanJob::fromArray($row);
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function findRunning(): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT * FROM scan_jobs WHERE status = :status ORDER BY started_at ASC');
|
||||
$stmt->execute(['status' => 'running']);
|
||||
$results = [];
|
||||
while ($row = $stmt->fetch()) {
|
||||
$results[] = ScanJob::fromArray($row);
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function save(ScanJob $job): void
|
||||
{
|
||||
$now = (new \DateTimeImmutable())->format('Y-m-d H:i:s');
|
||||
if ($job->id === null) {
|
||||
$stmt = $this->pdo->prepare(
|
||||
'INSERT INTO scan_jobs (type, status, network_range_id, device_id, created_by, created_at)
|
||||
VALUES (:type, :status, :network_range_id, :device_id, :created_by, :created_at)'
|
||||
);
|
||||
$stmt->execute([
|
||||
'type' => $job->type,
|
||||
'status' => $job->status,
|
||||
'network_range_id' => $job->networkRangeId,
|
||||
'device_id' => $job->deviceId,
|
||||
'created_by' => $job->createdBy,
|
||||
'created_at' => $now,
|
||||
]);
|
||||
$job->id = (int)$this->pdo->lastInsertId();
|
||||
} else {
|
||||
$stmt = $this->pdo->prepare(
|
||||
'UPDATE scan_jobs SET type = :type, status = :status, network_range_id = :network_range_id,
|
||||
device_id = :device_id, started_at = :started_at, finished_at = :finished_at,
|
||||
error_message = :error_message, result_json = :result_json
|
||||
WHERE id = :id'
|
||||
);
|
||||
$stmt->execute([
|
||||
'id' => $job->id,
|
||||
'type' => $job->type,
|
||||
'status' => $job->status,
|
||||
'network_range_id' => $job->networkRangeId,
|
||||
'device_id' => $job->deviceId,
|
||||
'started_at' => $job->startedAt?->format('Y-m-d H:i:s'),
|
||||
'finished_at' => $job->finishedAt?->format('Y-m-d H:i:s'),
|
||||
'error_message' => $job->errorMessage,
|
||||
'result_json' => $job->resultJson,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user