From a55542564e08ac9f29e50efce99168555132ed53 Mon Sep 17 00:00:00 2001 From: Agent Date: Sat, 6 Jun 2026 18:08:47 -0400 Subject: [PATCH 1/2] Add system users management --- routes/web.php | 2 + src/Controllers/ServerController.php | 151 ++++++++++++++++++++++++ views/servers/show.php | 4 + views/servers/system_users.php | 166 +++++++++++++++++++++++++++ 4 files changed, 323 insertions(+) create mode 100644 views/servers/system_users.php diff --git a/routes/web.php b/routes/web.php index b6e6877..3e6eaa1 100755 --- a/routes/web.php +++ b/routes/web.php @@ -52,6 +52,8 @@ $router->post('/servers/:id/nginx/site', [ServerController::class, 'nginxCreateS $router->delete('/servers/:id/nginx/site', [ServerController::class, 'nginxDeleteSite'], [AuthMiddleware::class, CSRFMiddleware::class]); $router->get('/servers/:id/ssl', [ServerController::class, 'ssl'], [AuthMiddleware::class]); $router->post('/servers/:id/ssl', [ServerController::class, 'sslAction'], [AuthMiddleware::class, CSRFMiddleware::class]); +$router->get('/servers/:id/users', [ServerController::class, 'systemUsers'], [AuthMiddleware::class]); +$router->post('/servers/:id/users', [ServerController::class, 'systemUsersAction'], [AuthMiddleware::class, CSRFMiddleware::class]); $router->get('/servers/:id/database', [ServerController::class, 'database'], [AuthMiddleware::class]); $router->post('/servers/:id/database', [ServerController::class, 'databaseAction'], [AuthMiddleware::class, CSRFMiddleware::class]); $router->post('/servers/:id/nginx', [ServerController::class, 'nginxAction'], [AuthMiddleware::class, CSRFMiddleware::class]); diff --git a/src/Controllers/ServerController.php b/src/Controllers/ServerController.php index cb61186..f8690d2 100755 --- a/src/Controllers/ServerController.php +++ b/src/Controllers/ServerController.php @@ -1475,6 +1475,157 @@ class ServerController $this->view->json(['success' => true, 'data' => $result]); } + public function systemUsers(int $id): void + { + $server = $this->serverModel->findById($id); + if (!$server) { $this->view->error(404); } + + try { + $ssh = new SSHService(); + if (!$ssh->connect($server)) { + throw new \RuntimeException('Could not establish SSH connection'); + } + $this->auditService->logCredentialAccess($id, 'View system users'); + + $data = []; + + $usersResult = $ssh->exec("awk -F: '\$3>=1000||\$3==0{printf \"%s|%s|%s|%s\\n\",\$1,\$3,\$6,\$7}' /etc/passwd 2>/dev/null | sort -t: -k2 -n | head -100"); + $users = []; + foreach (explode("\n", trim($usersResult['output'] ?? '')) as $line) { + $parts = explode("|", $line); + if (count($parts) >= 4 && is_numeric($parts[1])) { + $users[] = [ + 'username' => $parts[0], + 'uid' => (int) $parts[1], + 'home' => $parts[2], + 'shell' => $parts[3], + ]; + } + } + $data['users'] = $users; + + $groupsResult = $ssh->exec("awk -F: '{print \$1}' /etc/group 2>/dev/null | sort | head -50"); + $data['groups'] = array_filter(explode("\n", trim($groupsResult['output'] ?? ''))); + + $totalResult = $ssh->exec("awk -F: '\$3>=1000||\$3==0{print \$1}' /etc/passwd 2>/dev/null | wc -l"); + $data['total_users'] = (int) trim($totalResult['output'] ?? '0'); + + $lastResult = $ssh->exec("last -5 -w 2>/dev/null | head -10 || echo 'No login history'"); + $data['last_logins'] = trim($lastResult['output'] ?? ''); + + $ssh->disconnect(); + + $this->view->display('servers.system_users', [ + 'title' => 'System Users - ' . $server['name'] . ' - ServerManager', + 'server' => $server, + 'sysusers' => $data, + ]); + } catch (\Throwable $e) { + Session::setFlash('error', 'Failed to get users: ' . $e->getMessage()); + $this->view->redirect("/servers/{$id}"); + } + } + + public function systemUsersAction(int $id): void + { + $server = $this->serverModel->findById($id); + if (!$server) { + $this->view->json(['success' => false, 'message' => 'Server not found'], 404); + } + + $action = $_POST['action'] ?? ''; + $allowed = ['add', 'delete', 'lock', 'unlock', 'addgroup']; + if (!in_array($action, $allowed, true)) { + $this->view->json(['success' => false, 'message' => 'Invalid action']); + } + + try { + $ssh = new SSHService(); + if (!$ssh->connect($server)) { + throw new \RuntimeException('Could not establish SSH connection'); + } + $this->auditService->logServerAction($id, "sysuser_{$action}", $action); + + if ($action === 'add') { + $username = trim($_POST['username'] ?? ''); + $password = trim($_POST['password'] ?? ''); + $groups = trim($_POST['groups'] ?? ''); + $createHome = !empty($_POST['create_home']); + + if (empty($username) || empty($password)) { + $this->view->json(['success' => false, 'message' => 'Username and password required']); + } + if (!preg_match('/^[a-z_][a-z0-9_-]*$/', $username)) { + $this->view->json(['success' => false, 'message' => 'Invalid username']); + } + + $homeFlag = $createHome ? '-m' : '-M'; + $cmd = "sudo useradd {$homeFlag} -p \$(sudo openssl passwd -6 '{$password}')"; + if (!empty($groups)) { + $sanitized = preg_replace('/[^a-z0-9_,-]/', '', $groups); + $cmd .= " -G {$sanitized}"; + } + $cmd .= " {$username} 2>&1; echo EXIT:\$?"; + + $result = $ssh->exec($cmd); + $exitCode = (int) trim(explode('EXIT:', $result['output'] ?? 'EXIT:-1')[1] ?? '-1'); + $ssh->disconnect(); + + $this->view->json([ + 'success' => $exitCode === 0, + 'message' => $exitCode === 0 ? "User '{$username}' created." : 'Failed to create user.', + 'output' => trim($result['output']), + ]); + } elseif ($action === 'delete') { + $username = trim($_POST['username'] ?? ''); + $removeHome = !empty($_POST['remove_home']); + if (empty($username)) { + $this->view->json(['success' => false, 'message' => 'Username required']); + } + $rmFlag = $removeHome ? '-r' : ''; + $result = $ssh->exec("sudo userdel {$rmFlag} {$username} 2>&1; echo EXIT:\$?"); + $exitCode = (int) trim(explode('EXIT:', $result['output'] ?? 'EXIT:-1')[1] ?? '-1'); + $ssh->disconnect(); + $this->view->json([ + 'success' => $exitCode === 0, + 'message' => $exitCode === 0 ? "User '{$username}' deleted." : 'Failed to delete user.', + ]); + } elseif ($action === 'lock') { + $username = trim($_POST['username'] ?? ''); + if (empty($username)) $this->view->json(['success' => false, 'message' => 'Username required']); + $result = $ssh->exec("sudo passwd -l {$username} 2>&1; echo EXIT:\$?"); + $exitCode = (int) trim(explode('EXIT:', $result['output'] ?? 'EXIT:-1')[1] ?? '-1'); + $ssh->disconnect(); + $this->view->json([ + 'success' => $exitCode === 0, + 'message' => $exitCode === 0 ? "User '{$username}' locked." : 'Failed to lock user.', + ]); + } elseif ($action === 'unlock') { + $username = trim($_POST['username'] ?? ''); + if (empty($username)) $this->view->json(['success' => false, 'message' => 'Username required']); + $result = $ssh->exec("sudo passwd -u {$username} 2>&1; echo EXIT:\$?"); + $exitCode = (int) trim(explode('EXIT:', $result['output'] ?? 'EXIT:-1')[1] ?? '-1'); + $ssh->disconnect(); + $this->view->json([ + 'success' => $exitCode === 0, + 'message' => $exitCode === 0 ? "User '{$username}' unlocked." : 'Failed to unlock user.', + ]); + } elseif ($action === 'addgroup') { + $group = trim($_POST['group'] ?? ''); + if (empty($group)) $this->view->json(['success' => false, 'message' => 'Group name required']); + $result = $ssh->exec("sudo groupadd -f {$group} 2>&1; echo EXIT:\$?"); + $exitCode = (int) trim(explode('EXIT:', $result['output'] ?? 'EXIT:-1')[1] ?? '-1'); + $ssh->disconnect(); + $this->view->json([ + 'success' => $exitCode === 0, + 'message' => $exitCode === 0 ? "Group '{$group}' created." : 'Failed to create group.', + ]); + } + } catch (\Throwable $e) { + $this->view->json(['success' => false, 'message' => 'Failed: ' . $e->getMessage()]); + } + } + public function metrics(int $id): void { $monitoringService = new MonitoringService(); diff --git a/views/servers/show.php b/views/servers/show.php index b5777e2..cc3ea5a 100755 --- a/views/servers/show.php +++ b/views/servers/show.php @@ -168,6 +168,10 @@ $historicalMetrics = $historicalMetrics ?? []; Database Manager + + + System Users + + + + +
+
+
+
System Users
+
+
+
+
Add UserCreate new system user
+
+
+
+
Add GroupCreate new group
+
+
+ +
+

Users

+
+
+ + + + + + + + + + + + + +
UsernameUIDHomeShellActions
+ + + +
+
+
+
+ + +
+

Groups

+
+ +
+ + + +
+ +
+
+ + + +
+

Recent Logins

+
+
+
+
+
+
+ + + + + + + -- 2.43.0 From e0c4ec7a5313936057c7817e7bd941f42c041283 Mon Sep 17 00:00:00 2001 From: Agent Date: Sat, 6 Jun 2026 18:43:24 -0400 Subject: [PATCH 2/2] Fix system users: sudoers and nested sudo --- src/Controllers/ServerController.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Controllers/ServerController.php b/src/Controllers/ServerController.php index f8690d2..4be2504 100755 --- a/src/Controllers/ServerController.php +++ b/src/Controllers/ServerController.php @@ -1560,12 +1560,18 @@ class ServerController } $homeFlag = $createHome ? '-m' : '-M'; - $cmd = "sudo useradd {$homeFlag} -p \$(sudo openssl passwd -6 '{$password}')"; + $groupsFlag = ''; if (!empty($groups)) { $sanitized = preg_replace('/[^a-z0-9_,-]/', '', $groups); - $cmd .= " -G {$sanitized}"; + $groupsFlag = " -G {$sanitized}"; + } + $cmd = "sudo useradd {$homeFlag}{$groupsFlag} {$username} 2>&1; echo EXIT:\$?"; + $result = $ssh->exec($cmd); + $exitCode = (int) trim(explode('EXIT:', $result['output'] ?? 'EXIT:-1')[1] ?? '-1'); + if ($exitCode === 0) { + $b64 = base64_encode("{$username}:{$password}"); + $ssh->exec("echo {$b64} | base64 -d | sudo chpasswd 2>&1"); } - $cmd .= " {$username} 2>&1; echo EXIT:\$?"; $result = $ssh->exec($cmd); $exitCode = (int) trim(explode('EXIT:', $result['output'] ?? 'EXIT:-1')[1] ?? '-1'); -- 2.43.0