ServerManager project files
This commit is contained in:
195
src/Services/AuditService.php
Executable file
195
src/Services/AuditService.php
Executable file
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ServerManager\Services;
|
||||
|
||||
use ServerManager\Core\Database;
|
||||
use ServerManager\Core\Session;
|
||||
use ServerManager\Core\Logger;
|
||||
|
||||
class AuditService
|
||||
{
|
||||
private Database $db;
|
||||
private Logger $logger;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->db = Database::getInstance();
|
||||
$this->logger = Logger::getInstance();
|
||||
}
|
||||
|
||||
public function log(
|
||||
string $action,
|
||||
string $entityType,
|
||||
?int $entityId,
|
||||
?array $details = null,
|
||||
?array $metadata = null
|
||||
): void {
|
||||
$userId = Session::get('user_id');
|
||||
$username = Session::get('username');
|
||||
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
||||
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? 'unknown';
|
||||
$now = date('Y-m-d H:i:s');
|
||||
|
||||
$this->db->insert('audit_logs', [
|
||||
'user_id' => $userId,
|
||||
'username' => $username ?? 'system',
|
||||
'action' => $action,
|
||||
'entity_type' => $entityType,
|
||||
'entity_id' => $entityId,
|
||||
'details' => $details ? json_encode($details, JSON_UNESCAPED_SLASHES) : null,
|
||||
'ip_address' => $ip,
|
||||
'user_agent' => $userAgent,
|
||||
'created_at' => $now,
|
||||
]);
|
||||
|
||||
$logMessage = sprintf(
|
||||
'[AUDIT] %s | %s | %s:%s | User: %s (%d)',
|
||||
$action,
|
||||
$entityType,
|
||||
$entityId ?? 'N/A',
|
||||
$ip,
|
||||
$username ?? 'system',
|
||||
$userId ?? 0
|
||||
);
|
||||
|
||||
$this->logger->info($logMessage);
|
||||
|
||||
if (in_array($action, ['credential_access', 'credential_decrypt', 'credential_use'])) {
|
||||
$this->logger->warning("[CREDENTIAL_ACCESS] {$logMessage}");
|
||||
}
|
||||
}
|
||||
|
||||
public function logCredentialAccess(int $serverId, string $reason): void
|
||||
{
|
||||
$server = $this->db->fetch('SELECT name, ip_address FROM servers WHERE id = ?', [$serverId]);
|
||||
$serverName = $server['name'] ?? 'Unknown';
|
||||
|
||||
$this->log(
|
||||
'credential_access',
|
||||
'server_credentials',
|
||||
$serverId,
|
||||
[
|
||||
'server_name' => $serverName,
|
||||
'ip_address' => $server['ip_address'] ?? 'unknown',
|
||||
'reason' => $reason,
|
||||
'timestamp' => date('c'),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function logLogin(string $username, bool $success, ?string $reason = null): void
|
||||
{
|
||||
$this->log(
|
||||
$success ? 'login_success' : 'login_failed',
|
||||
'user',
|
||||
null,
|
||||
[
|
||||
'username' => $username,
|
||||
'success' => $success,
|
||||
'reason' => $reason,
|
||||
]
|
||||
);
|
||||
|
||||
if (!$success) {
|
||||
$this->logger->warning("[SECURITY] Failed login attempt for username: {$username}", [
|
||||
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function logServerAction(int $serverId, string $action, string $command): void
|
||||
{
|
||||
$this->log(
|
||||
'server_action',
|
||||
'server',
|
||||
$serverId,
|
||||
[
|
||||
'action' => $action,
|
||||
'command' => $command,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function logUserManagement(int $userId, string $action): void
|
||||
{
|
||||
$this->log(
|
||||
$action,
|
||||
'user',
|
||||
$userId,
|
||||
[
|
||||
'performed_by' => Session::get('username'),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function getRecentActivity(int $limit = 20): array
|
||||
{
|
||||
return $this->db->fetchAll(
|
||||
'SELECT al.*, u.username as actor_name
|
||||
FROM audit_logs al
|
||||
LEFT JOIN users u ON al.user_id = u.id
|
||||
ORDER BY al.created_at DESC
|
||||
LIMIT ?',
|
||||
[$limit]
|
||||
);
|
||||
}
|
||||
|
||||
public function getAuditLogs(int $page = 1, int $perPage = 50, array $filters = []): array
|
||||
{
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
if (!empty($filters['action'])) {
|
||||
$where[] = 'al.action = ?';
|
||||
$params[] = $filters['action'];
|
||||
}
|
||||
|
||||
if (!empty($filters['user_id'])) {
|
||||
$where[] = 'al.user_id = ?';
|
||||
$params[] = $filters['user_id'];
|
||||
}
|
||||
|
||||
if (!empty($filters['entity_type'])) {
|
||||
$where[] = 'al.entity_type = ?';
|
||||
$params[] = $filters['entity_type'];
|
||||
}
|
||||
|
||||
if (!empty($filters['date_from'])) {
|
||||
$where[] = 'al.created_at >= ?';
|
||||
$params[] = $filters['date_from'];
|
||||
}
|
||||
|
||||
if (!empty($filters['date_to'])) {
|
||||
$where[] = 'al.created_at <= ?';
|
||||
$params[] = $filters['date_to'];
|
||||
}
|
||||
|
||||
$whereClause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
$offset = ($page - 1) * $perPage;
|
||||
|
||||
$total = $this->db->fetch(
|
||||
"SELECT COUNT(*) as total FROM audit_logs al {$whereClause}",
|
||||
$params
|
||||
);
|
||||
|
||||
$logs = $this->db->fetchAll(
|
||||
"SELECT al.*, u.username as actor_name
|
||||
FROM audit_logs al
|
||||
LEFT JOIN users u ON al.user_id = u.id
|
||||
{$whereClause}
|
||||
ORDER BY al.created_at DESC
|
||||
LIMIT ? OFFSET ?",
|
||||
array_merge($params, [$perPage, $offset])
|
||||
);
|
||||
|
||||
return [
|
||||
'data' => $logs,
|
||||
'total' => (int) ($total['total'] ?? 0),
|
||||
'page' => $page,
|
||||
'per_page' => $perPage,
|
||||
'total_pages' => ceil((int) ($total['total'] ?? 0) / $perPage),
|
||||
];
|
||||
}
|
||||
}
|
||||
201
src/Services/MonitoringService.php
Executable file
201
src/Services/MonitoringService.php
Executable file
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ServerManager\Services;
|
||||
|
||||
use ServerManager\Core\Database;
|
||||
use ServerManager\Core\Logger;
|
||||
use ServerManager\Core\App;
|
||||
use ServerManager\Services\SSHService;
|
||||
|
||||
class MonitoringService
|
||||
{
|
||||
private Database $db;
|
||||
private Logger $logger;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->db = Database::getInstance();
|
||||
$this->logger = Logger::getInstance();
|
||||
}
|
||||
|
||||
public function collectMetrics(int $serverId): ?array
|
||||
{
|
||||
$server = $this->db->fetch('SELECT * FROM servers WHERE id = ?', [$serverId]);
|
||||
|
||||
if (!$server || !$server['is_active']) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$server = App::getEncryption()->decryptArray($server, ['ssh_password', 'ssh_key']);
|
||||
|
||||
try {
|
||||
$ssh = new SSHService();
|
||||
|
||||
if (!$ssh->connect($server, true)) {
|
||||
return ['status' => 'offline'];
|
||||
}
|
||||
|
||||
$metrics = [
|
||||
'server_id' => $serverId,
|
||||
'status' => 'online',
|
||||
'cpu_usage' => $this->getCPUUsage($ssh),
|
||||
'ram_usage' => $this->getRAMUsage($ssh),
|
||||
'disk_usage' => $this->getDiskUsage($ssh),
|
||||
'load_average' => $this->getLoadAverage($ssh),
|
||||
'uptime' => $this->getUptime($ssh),
|
||||
];
|
||||
|
||||
$this->saveMetrics($metrics);
|
||||
|
||||
$ssh->disconnect();
|
||||
|
||||
return $metrics;
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->warning("Failed to collect metrics for server {$serverId}: " . $e->getMessage());
|
||||
|
||||
$this->saveMetrics([
|
||||
'server_id' => $serverId,
|
||||
'status' => 'offline',
|
||||
'cpu_usage' => 0,
|
||||
'ram_usage' => 0,
|
||||
'disk_usage' => 0,
|
||||
'load_average' => 0,
|
||||
'uptime' => '',
|
||||
]);
|
||||
|
||||
return ['status' => 'offline'];
|
||||
}
|
||||
}
|
||||
|
||||
public function collectAllMetrics(): array
|
||||
{
|
||||
$servers = $this->db->fetchAll('SELECT * FROM servers WHERE is_active = 1');
|
||||
$results = [];
|
||||
|
||||
foreach ($servers as $server) {
|
||||
$results[$server['id']] = $this->collectMetrics($server['id']);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
private function getCPUUsage(SSHService $ssh): float
|
||||
{
|
||||
$result = $ssh->exec("top -bn1 | grep 'Cpu(s)' | awk '{print $2}' | cut -d'%' -f1");
|
||||
return (float) trim($result['output']);
|
||||
}
|
||||
|
||||
private function getRAMUsage(SSHService $ssh): float
|
||||
{
|
||||
$result = $ssh->exec("free | grep Mem | awk '{printf \"%.2f\", $3/$2 * 100}'");
|
||||
return (float) trim($result['output']);
|
||||
}
|
||||
|
||||
private function getDiskUsage(SSHService $ssh): float
|
||||
{
|
||||
$result = $ssh->exec("df / | tail -1 | awk '{print $5}' | sed 's/%//'");
|
||||
return (float) trim($result['output']);
|
||||
}
|
||||
|
||||
private function getLoadAverage(SSHService $ssh): float
|
||||
{
|
||||
$result = $ssh->exec("cat /proc/loadavg | awk '{print $1}'");
|
||||
return (float) trim($result['output']);
|
||||
}
|
||||
|
||||
private function getUptime(SSHService $ssh): string
|
||||
{
|
||||
$result = $ssh->exec("uptime -p");
|
||||
return trim(str_replace('up ', '', $result['output']));
|
||||
}
|
||||
|
||||
private function saveMetrics(array $metrics): void
|
||||
{
|
||||
$now = date('Y-m-d H:i:s');
|
||||
|
||||
$this->db->insert('monitoring_history', [
|
||||
'server_id' => $metrics['server_id'],
|
||||
'status' => $metrics['status'],
|
||||
'cpu_usage' => $metrics['cpu_usage'],
|
||||
'ram_usage' => $metrics['ram_usage'],
|
||||
'disk_usage' => $metrics['disk_usage'],
|
||||
'load_average' => $metrics['load_average'],
|
||||
'uptime' => $metrics['uptime'] ?? '',
|
||||
'created_at' => $now,
|
||||
]);
|
||||
|
||||
$this->db->query(
|
||||
"UPDATE servers SET
|
||||
last_check_at = ?,
|
||||
current_status = ?,
|
||||
cpu_usage = ?,
|
||||
ram_usage = ?,
|
||||
disk_usage = ?,
|
||||
load_average = ?,
|
||||
uptime = ?
|
||||
WHERE id = ?",
|
||||
[
|
||||
$now,
|
||||
$metrics['status'],
|
||||
$metrics['cpu_usage'],
|
||||
$metrics['ram_usage'],
|
||||
$metrics['disk_usage'],
|
||||
$metrics['load_average'],
|
||||
$metrics['uptime'] ?? '',
|
||||
$metrics['server_id'],
|
||||
]
|
||||
);
|
||||
|
||||
$retention = App::getConfig()['monitoring']['retention_days'] ?? 30;
|
||||
$this->db->query(
|
||||
"DELETE FROM monitoring_history WHERE created_at < DATE_SUB(NOW(), INTERVAL ? DAY)",
|
||||
[$retention]
|
||||
);
|
||||
}
|
||||
|
||||
public function getLatestMetrics(int $serverId): ?array
|
||||
{
|
||||
return $this->db->fetch(
|
||||
'SELECT * FROM monitoring_history WHERE server_id = ? ORDER BY created_at DESC LIMIT 1',
|
||||
[$serverId]
|
||||
);
|
||||
}
|
||||
|
||||
public function getHistoricalMetrics(int $serverId, int $hours = 24): array
|
||||
{
|
||||
return $this->db->fetchAll(
|
||||
'SELECT * FROM monitoring_history
|
||||
WHERE server_id = ? AND created_at >= DATE_SUB(NOW(), INTERVAL ? HOUR)
|
||||
ORDER BY created_at ASC',
|
||||
[$serverId, $hours]
|
||||
);
|
||||
}
|
||||
|
||||
public function getDashboardStats(): array
|
||||
{
|
||||
$totalServers = $this->db->fetch("SELECT COUNT(*) as total FROM servers");
|
||||
$onlineServers = $this->db->fetch("SELECT COUNT(*) as total FROM servers WHERE current_status = 'online' AND is_active = 1");
|
||||
$offlineServers = $this->db->fetch("SELECT COUNT(*) as total FROM servers WHERE current_status = 'offline' AND is_active = 1");
|
||||
|
||||
$avgCpu = $this->db->fetch(
|
||||
"SELECT AVG(cpu_usage) as avg FROM servers WHERE is_active = 1 AND current_status = 'online'"
|
||||
);
|
||||
$avgRam = $this->db->fetch(
|
||||
"SELECT AVG(ram_usage) as avg FROM servers WHERE is_active = 1 AND current_status = 'online'"
|
||||
);
|
||||
$avgDisk = $this->db->fetch(
|
||||
"SELECT AVG(disk_usage) as avg FROM servers WHERE is_active = 1 AND current_status = 'online'"
|
||||
);
|
||||
|
||||
return [
|
||||
'total_servers' => (int) ($totalServers['total'] ?? 0),
|
||||
'online_servers' => (int) ($onlineServers['total'] ?? 0),
|
||||
'offline_servers' => (int) ($offlineServers['total'] ?? 0),
|
||||
'avg_cpu' => round((float) ($avgCpu['avg'] ?? 0), 1),
|
||||
'avg_ram' => round((float) ($avgRam['avg'] ?? 0), 1),
|
||||
'avg_disk' => round((float) ($avgDisk['avg'] ?? 0), 1),
|
||||
];
|
||||
}
|
||||
}
|
||||
168
src/Services/SSHService.php
Executable file
168
src/Services/SSHService.php
Executable file
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ServerManager\Services;
|
||||
|
||||
use phpseclib3\Net\SSH2;
|
||||
use phpseclib3\Crypt\PublicKeyLoader;
|
||||
use ServerManager\Core\App;
|
||||
use ServerManager\Core\Encryption;
|
||||
use ServerManager\Core\Logger;
|
||||
|
||||
class SSHService
|
||||
{
|
||||
private ?SSH2 $connection = null;
|
||||
private array $serverConfig;
|
||||
private Logger $logger;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->logger = Logger::getInstance();
|
||||
}
|
||||
|
||||
public function connect(array $server, bool $forceReconnect = false): bool
|
||||
{
|
||||
if ($this->connection !== null && !$forceReconnect) {
|
||||
return $this->connection->isConnected();
|
||||
}
|
||||
|
||||
$this->serverConfig = $server;
|
||||
$config = App::getConfig()['ssh'];
|
||||
|
||||
try {
|
||||
$host = $server['ip_address'] ?? $server['hostname'];
|
||||
$port = (int) ($server['ssh_port'] ?? 22);
|
||||
|
||||
$this->connection = new SSH2($host, $port, $config['timeout'] ?? 30);
|
||||
|
||||
if (!empty($server['ssh_key'])) {
|
||||
$key = PublicKeyLoader::load($server['ssh_key']);
|
||||
$result = $this->connection->login($server['ssh_user'], $key);
|
||||
} elseif (!empty($server['ssh_password'])) {
|
||||
$result = $this->connection->login($server['ssh_user'], $server['ssh_password']);
|
||||
} else {
|
||||
throw new \RuntimeException('No authentication method configured');
|
||||
}
|
||||
|
||||
if (!$result) {
|
||||
throw new \RuntimeException('SSH authentication failed for ' . $host);
|
||||
}
|
||||
|
||||
if ($config['keepalive_interval'] ?? 0 > 0) {
|
||||
$this->connection->setKeepAlive($config['keepalive_interval']);
|
||||
}
|
||||
|
||||
$this->logger->info("SSH connection established to {$host}");
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error("SSH connection failed: " . $e->getMessage(), [
|
||||
'host' => $host ?? 'unknown',
|
||||
'port' => $port ?? 22,
|
||||
]);
|
||||
$this->connection = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function disconnect(): void
|
||||
{
|
||||
if ($this->connection !== null) {
|
||||
$this->connection->disconnect();
|
||||
$this->connection = null;
|
||||
}
|
||||
}
|
||||
|
||||
public function exec(string $command, int $timeout = 0): array
|
||||
{
|
||||
if ($this->connection === null || !$this->connection->isConnected()) {
|
||||
throw new \RuntimeException('No active SSH connection');
|
||||
}
|
||||
|
||||
$timeout = $timeout ?: (App::getConfig()['ssh']['command_timeout'] ?? 60);
|
||||
|
||||
$this->connection->setTimeout($timeout);
|
||||
|
||||
$output = $this->connection->exec($command);
|
||||
$exitStatus = $this->connection->getExitStatus();
|
||||
$stderr = $this->connection->getStdError() ?? '';
|
||||
|
||||
$this->logger->info("SSH command executed", [
|
||||
'command' => $command,
|
||||
'exit_status' => $exitStatus,
|
||||
]);
|
||||
|
||||
return [
|
||||
'output' => $output,
|
||||
'exit_status' => $exitStatus,
|
||||
'stderr' => $stderr,
|
||||
];
|
||||
}
|
||||
|
||||
public function write(string $data): void
|
||||
{
|
||||
if ($this->connection !== null && $this->connection->isConnected()) {
|
||||
$this->connection->write($data);
|
||||
}
|
||||
}
|
||||
|
||||
public function read(string $expected = '', int $mode = SSH2::READ_REGEX): string
|
||||
{
|
||||
if ($this->connection === null || !$this->connection->isConnected()) {
|
||||
throw new \RuntimeException('No active SSH connection');
|
||||
}
|
||||
return $this->connection->read($expected, $mode);
|
||||
}
|
||||
|
||||
public function getInteractiveShell(): ?SSH2
|
||||
{
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
public function testConnection(array $server): bool
|
||||
{
|
||||
try {
|
||||
$result = $this->connect($server, true);
|
||||
if ($result) {
|
||||
$this->disconnect();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->warning("SSH connection test failed", ['error' => $e->getMessage()]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getSystemInfo(): array
|
||||
{
|
||||
$commands = [
|
||||
'hostname' => 'hostname',
|
||||
'os' => 'cat /etc/os-release | head -1',
|
||||
'kernel' => 'uname -r',
|
||||
'uptime' => 'uptime -p',
|
||||
'cpu_model' => 'cat /proc/cpuinfo | grep "model name" | head -1',
|
||||
'cpu_cores' => 'nproc',
|
||||
'mem_total' => 'free -b | grep Mem | awk \'{print $2}\'',
|
||||
'mem_used' => "free -b | grep Mem | awk '{print $3}'",
|
||||
'mem_avail' => "free -b | grep Mem | awk '{print $7}'",
|
||||
'disk_total' => "df -B1 / | tail -1 | awk '{print $2}'",
|
||||
'disk_used' => "df -B1 / | tail -1 | awk '{print $3}'",
|
||||
'disk_avail' => "df -B1 / | tail -1 | awk '{print $4}'",
|
||||
'disk_percent' => "df / | tail -1 | awk '{print $5}' | sed 's/%//'",
|
||||
];
|
||||
|
||||
$info = [];
|
||||
foreach ($commands as $key => $cmd) {
|
||||
$result = $this->exec($cmd);
|
||||
$info[$key] = trim($result['output']);
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->disconnect();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user