mirror of
https://github.com/FOSSBilling/Proxmox.git
synced 2026-11-03 02:27:31 +09:00
This is substantial work towards #26 and an eventual release of 0.1.0 * Fixed Divide by 0 if a Storage is not reachable or has 0 in any value * Fixed small things, started storage class edit * Removed broken is_configured * Fixed API Wrapper <script> tag location * Added functionality to save new tags to the database * Many UI Improvements, added Help to Server View, Ability to add Storage classes to storage * Added functionality to Enable & Disable QEMU VM Templates * - Replaced pve2-api-php with fork that uses symfony & httpclient. - Reworked some of the server preparation functionality - Added "Debug mode" to proxmox Settings * Set up automated release process * Added phpstan workflow * Added local phpstan config file (phpstan.dist.neon) * Added Tests & workflow to run phpunit from FOSSBilling * Fixed JS for CIDR/IP Validation * Cleanup (Comments etc)
270 lines
12 KiB
PHP
270 lines
12 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Proxmox module for FOSSBilling
|
|
*
|
|
* @author FOSSBilling (https://www.fossbilling.org) & Anuril (https://github.com/anuril)
|
|
* @license GNU General Public License version 3 (GPLv3)
|
|
*
|
|
* This software may contain code previously used in the BoxBilling project.
|
|
* Copyright BoxBilling, Inc 2011-2021
|
|
* Original Author: Scitch (https://github.com/scitch)
|
|
*
|
|
* This source file is subject to the GNU General Public License version 3 (GPLv3) that is bundled
|
|
* with this source code in the file LICENSE.
|
|
* This Module has been written originally by Scitch (https://github.com/scitch) and has been forked from the original BoxBilling Module.
|
|
* It has been rewritten extensively.
|
|
*/
|
|
|
|
namespace Box\Mod\Serviceproxmox;
|
|
|
|
|
|
/**
|
|
* Authentication Class Trait for FOSSSBilling Proxmox Module
|
|
*
|
|
* This class trait contains all the functions that are used to manage the authentication inside the Proxmox Module.
|
|
*
|
|
*/
|
|
trait ProxmoxAuthentication
|
|
{
|
|
/* ################################################################################################### */
|
|
/* ######################################## Permissions ############################################ */
|
|
/* ################################################################################################### */
|
|
|
|
/**
|
|
* Function to set up proxmox server for fossbilling
|
|
* @param $server - server object
|
|
* @return bool - array of permission information
|
|
*/
|
|
public function prepare_pve_setup($server)
|
|
{
|
|
$proxmox = $this->getProxmoxInstance($server);
|
|
// Attempt to log in to the server using the API
|
|
if (!$proxmox->login()) {
|
|
throw new \Box_Exception("Failed to log in to the proxmox server. Check username & password and try again.");
|
|
}
|
|
|
|
// If this code runs, login already worked, so either username + password or token login worked.
|
|
// Create an API token for the Admin user if there is both tokenname and tokenvalue already present
|
|
if (empty($server->tokenname) || empty($server->tokenvalue)) {
|
|
// Check if the connecting user has the 'Realm.AllocateUser' permission
|
|
$permissions = $proxmox->get("/access/permissions");
|
|
$found_permission = 0;
|
|
// Iterate through the permissions and check for 'Realm.AllocateUser' permission
|
|
foreach ($permissions as $permission) {
|
|
if ($permission['Realm.AllocateUser'] == 1) {
|
|
$found_permission += 1;
|
|
}
|
|
}
|
|
// Throw an exception if the 'Realm.AllocateUser' permission is not found
|
|
if (!$found_permission) {
|
|
throw new \Box_Exception("User does not have 'Realm.AllocateUser' permission");
|
|
}
|
|
|
|
// Validate if there already is a group for fossbilling
|
|
$groups = $proxmox->get("/access/groups");
|
|
$foundgroups = 0;
|
|
// Iterate through the groups and check for a group beginning with 'fossbilling'
|
|
foreach ($groups as $group) {
|
|
if (strpos($group['groupid'], 'fossbilling') === 0) {
|
|
$foundgroups += 1;
|
|
$groupid = $group['groupid'];
|
|
}
|
|
}
|
|
// Handle the cases where there are no groups, one group, or multiple groups
|
|
switch ($foundgroups) {
|
|
case 0:
|
|
// Create a new group
|
|
$groupid = 'fossbilling_' . rand(1000, 9999);
|
|
$proxmox->post("/access/groups", array('groupid' => $groupid, 'comment' => 'fossbilling group'));
|
|
break;
|
|
case 1:
|
|
// Use the existing group
|
|
break;
|
|
default:
|
|
throw new \Box_Exception("More than one group found");
|
|
break;
|
|
}
|
|
|
|
// Validate if there already is a user and token for fossbilling
|
|
$users = $proxmox->get("/access/users");
|
|
$found = 0;
|
|
// Iterate through the users and check for a user beginning with 'fb'
|
|
foreach ($users as $user) {
|
|
if (strpos($user['userid'], 'fb') === 0) {
|
|
$found += 1;
|
|
$userid = $user['userid'];
|
|
}
|
|
// Handle the cases where there are no users, one user, or multiple users
|
|
switch ($found) {
|
|
case 0:
|
|
// Create a new user
|
|
$userid = 'fb_' . rand(1000, 9999) . '@pve'; // TODO: Make realm configurable in the module settings
|
|
// $groupid has to be defined because it is set in the switch statement above, otherwise it would throw an exception. $proxmox also, because otherwise, login would fail and break.
|
|
$proxmox->post("/access/users", array('userid' => $userid, 'password' => $this->di['tools'], 'enable' => 1, 'comment' => 'fossbilling user', 'groups' => $groupid)); /* @phpstan-ignore-line */
|
|
|
|
// Create a token for the new user
|
|
$token = $proxmox->post("/access/users/" . $userid . "/token/fb_access", array()); /* @phpstan-ignore-line Proxmox is set, otherwise code errors out */
|
|
|
|
// Check if the token was created successfully
|
|
if ($token) {
|
|
$server->tokenname = $token['full-tokenid'];
|
|
$server->tokenvalue = $token['value'];
|
|
} else {
|
|
throw new \Box_Exception("Failed to create token for fossbilling user");
|
|
break;
|
|
}
|
|
break;
|
|
case 1:
|
|
// Create a token for the existing user
|
|
$token = $proxmox->post("/access/users/" . $userid . "/token/fb_access", array());/* @phpstan-ignore-line Proxmox is set, otherwise code errors out */
|
|
if ($token) {
|
|
$server->tokenname = $token['full-tokenid'];
|
|
$server->tokenvalue = $token['value'];
|
|
} else {
|
|
throw new \Box_Exception("Failed to create token for fossbilling user");
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
throw new \Box_Exception("There are more than one fossbilling users on the server. Please delete all but one.");
|
|
break;
|
|
}
|
|
// Create permissions for the newly created token
|
|
// Set up permissions for the token (Admin user) to manage users, groups, and other administrative tasks
|
|
$permissions = $proxmox->put("/access/acl/", array('path' => '/', 'roles' => 'PVEUserAdmin', 'propagate' => 1, 'users' => $userid)); /* @phpstan-ignore-line */
|
|
$permissions = $proxmox->put("/access/acl/", array('path' => '/', 'roles' => 'PVEAuditor', 'propagate' => 1, 'users' => $userid)); /* @phpstan-ignore-line */
|
|
$permissions = $proxmox->put("/access/acl/", array('path' => '/', 'roles' => 'PVESysAdmin', 'propagate' => 1, 'users' => $userid)); /* @phpstan-ignore-line */
|
|
$permissions = $proxmox->put("/access/acl/", array('path' => '/', 'roles' => 'PVEPoolAdmin', 'propagate' => 1, 'users' => $userid)); /* @phpstan-ignore-line */
|
|
$permissions = $proxmox->put("/access/acl/", array('path' => '/', 'roles' => 'PVEDatastoreAdmin', 'propagate' => 1, 'users' => $userid)); /* @phpstan-ignore-line */
|
|
$permissions = $proxmox->put("/access/acl/", array('path' => '/', 'roles' => 'PVEUserAdmin', 'propagate' => 1, 'tokens' => $server->tokenname));
|
|
$permissions = $proxmox->put("/access/acl/", array('path' => '/', 'roles' => 'PVEAuditor', 'propagate' => 1, 'tokens' => $server->tokenname));
|
|
$permissions = $proxmox->put("/access/acl/", array('path' => '/', 'roles' => 'PVESysAdmin', 'propagate' => 1, 'tokens' => $server->tokenname));
|
|
$permissions = $proxmox->put("/access/acl/", array('path' => '/', 'roles' => 'PVEPoolAdmin', 'propagate' => 1, 'tokens' => $server->tokenname));
|
|
$permissions = $proxmox->put("/access/acl/", array('path' => '/', 'roles' => 'PVEDatastoreAdmin', 'propagate' => 1, 'tokens' => $server->tokenname));
|
|
|
|
// Sleep for 5 seconds
|
|
sleep(5);
|
|
|
|
// Delete the root password and unset the PVE2_API instance
|
|
$server->root_password = null;
|
|
unset($proxmox);
|
|
|
|
// Return the test_access result for the server
|
|
return $this->test_access($server);
|
|
}
|
|
} elseif (!empty($server->tokenname) && !empty($server->tokenvalue)) {
|
|
// Validate Permissions for the token
|
|
$permissions = $proxmox->get("/access/acl/");
|
|
// Check for 'PVEUserAdmin', 'PVEAuditor', 'PVESysAdmin', 'PVEPoolAdmin', and 'PVEDatastoreAdmin' permissions, and if they don't exist, try to create them.
|
|
$required_permissions = array('PVEUserAdmin', 'PVEAuditor', 'PVESysAdmin', 'PVEPoolAdmin', 'PVEDatastoreAdmin');
|
|
foreach ($required_permissions as $permission) {
|
|
$found_permission = 0;
|
|
foreach ($permissions as $acl) {
|
|
if ($acl['roleid'] == $permission) {
|
|
$found_permission += 1;
|
|
}
|
|
}
|
|
if (!$found_permission) {
|
|
$permissions = $proxmox->put("/access/acl/", array('path' => '/', 'roles' => $permission, 'propagate' => 1, 'tokens' => $server->tokenname));
|
|
}
|
|
}
|
|
return $this->test_access($server);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Tests the access to the server
|
|
*
|
|
* @param Server $server The server to test
|
|
* @return bool True if the test was successful, false otherwise
|
|
* @throws Box_Exception
|
|
*/
|
|
public function test_access($server)
|
|
{
|
|
$proxmox = $this->getProxmoxInstance($server);
|
|
// Attempt to log in to the server using the API
|
|
if (!$proxmox->login()) {
|
|
throw new \Box_Exception("Failed to connect to the server. testpmx");
|
|
}
|
|
|
|
// Generate a random test user ID
|
|
$userid = 'tfb_' . rand(1000, 9999) . '@pve'; // TODO: Make realm configurable in the module settings
|
|
|
|
// Create a new user for testing purposes
|
|
$proxmox->post("/access/users", array('userid' => $userid, 'password' => $this->di['tools']->generatePassword(16, 4), 'enable' => '1', 'comment' => 'FOSSBilling test user ' . $userid));
|
|
|
|
// Retrieve the newly created user
|
|
$newuser = $proxmox->get("/access/users/" . $userid);
|
|
|
|
// Check if the new user was successfully created
|
|
if (!$newuser) {
|
|
throw new \Box_Exception("Failed to create test user for fossbilling");
|
|
} else {
|
|
// Delete the test user
|
|
$deleteuser = $proxmox->delete("/access/users/" . $userid);
|
|
|
|
// Check if the test user was successfully deleted
|
|
$deleteuser = $proxmox->get("/access/users/" . $userid);
|
|
if ($deleteuser) {
|
|
throw new \Box_Exception("Failed to delete test user for fossbilling. Check permissions.");
|
|
} else {
|
|
// Remove the root password from the server object
|
|
$server->root_password = null;
|
|
return $server;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Creates a new client user on the server
|
|
*
|
|
* @param Server $server The server to create the user on
|
|
* @param Client $client The client to create the user for
|
|
* @return void
|
|
*/
|
|
public function create_client_user($server, $client)
|
|
{
|
|
$clientuser = $this->di['db']->dispense('service_proxmox_users');
|
|
$clientuser->client_id = $client->id;
|
|
$this->di['db']->store($clientuser);
|
|
$proxmox = $this->getProxmoxInstance($server);
|
|
if (!$proxmox->login()) {
|
|
throw new \Box_Exception("Failed to connect to the server. create_client_user");
|
|
}
|
|
$userid = 'fb_customer_' . $client->id . '@pve'; // TODO: Make realm configurable in the module settings
|
|
$newuser = $proxmox->post("/access/users", array('userid' => $userid, 'password' => $this->di['tools']->generatePassword(16, 4), 'enable' => '1', 'comment' => 'fossbilling user ' . $client->id));
|
|
$newuser = $proxmox->get("/access/users/" . $userid);
|
|
|
|
// Create Token for Client
|
|
$clientuser->admin_tokenname = 'fb_admin_' . $client->id;
|
|
$clientuser->server_id = $server->id;
|
|
$admintoken_response = $proxmox->post("/access/users/" . $userid . "/token/" . $clientuser->admin_tokenname, array('comment' => 'fossbilling admin token for client id: ' . $client->id));
|
|
$clientuser->admin_tokenname = $admintoken_response['full-tokenid'];
|
|
$clientuser->admin_tokenvalue = $admintoken_response['value'];
|
|
$clientuser->view_tokenname = 'fb_view_' . $client->id;
|
|
$viewtoken_response = $proxmox->post("/access/users/" . $userid . "/token/" . $clientuser->view_tokenname, array('comment' => 'fossbilling view token for client id: ' . $client->id));
|
|
$clientuser->view_tokenname = $viewtoken_response['full-tokenid'];
|
|
$clientuser->view_tokenvalue = $viewtoken_response['value'];
|
|
|
|
|
|
$this->di['db']->store($clientuser);
|
|
|
|
// Check if the client already has a pool and if not create it.
|
|
$pool = $proxmox->get("/pools/" . $client->id);
|
|
if (!$pool) {
|
|
$pool = $proxmox->post("/pools", array('poolid' => 'fb_client_' . $client->id, 'comment' => 'fossbilling pool for client id: ' . $client->id));
|
|
}
|
|
// Add permissions for client
|
|
|
|
$permissions = $proxmox->put("/access/acl/", array('path' => '/pool/' . 'fb_client_' . $client->id, 'roles' => 'PVEVMUser,PVEVMAdmin,PVEDatastoreAdmin,PVEDatastoreUser', 'propagate' => 1, 'users' => $userid));
|
|
|
|
$permissions = $proxmox->put("/access/acl/", array('path' => '/pool/' . 'fb_client_' . $client->id, 'roles' => 'PVEVMUser,PVEDatastoreUser', 'propagate' => 1, 'tokens' => $clientuser->view_tokenname));
|
|
|
|
$permissions = $proxmox->put("/access/acl/", array('path' => '/pool/' . 'fb_client_' . $client->id, 'roles' => 'PVEVMAdmin,PVEDatastoreAdmin', 'propagate' => 1, 'tokens' => $clientuser->admin_tokenname));
|
|
}
|
|
}
|