Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ Example - Return status array for each Proxmox Host in this cluster.
require_once("./pve2-api-php-client/pve2_api.class.php");

# You can try/catch exception handle the constructor here if you want.

## EXAMPLE: Login with username and password ##
$pve2 = new PVE2_API("hostname", "username", "realm", "password");
# realm above can be pve, pam or any other realm available.

Expand All @@ -28,6 +30,22 @@ Example - Return status array for each Proxmox Host in this cluster.
exit;
}


## EXAMPLE: Login with API Token ##
$pve2 = new PVE2_API("hostname", "username", "realm", "TOKENID:TOKENVALUE");
# realm above can be pve, pam or any other realm available.

if ($pve2->login()) {
foreach ($pve2->get_node_list() as $node_name) {
print_r($pve2->get("/nodes/".$node_name."/status"));
}
} else {
print("Login to Proxmox Host failed.\n");
exit;
}



Example - Create a new Linux Container (LXC) on the first host in the cluster.

require_once("./pve2-api-php-client/pve2_api.class.php");
Expand Down
153 changes: 101 additions & 52 deletions pve2_api.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,63 +62,98 @@ public function __construct ($hostname, $username, $realm, $password, $port = 80
$this->password = $password;
$this->port = $port;
$this->verify_ssl = $verify_ssl;

// parse password if there a : in password, it seems to be token authentification
$password_parser = explode(":", $this->password);

if(count($password_parser) >= 2){
$this->authToken = "{$this->username}@{$this->realm}!{$password_parser[0]}={$password_parser[1]}";
}

}

/*
* bool login ()
* Performs login to PVE Server using JSON API, and obtains Access Ticket.
*/
public function login () {
// Prepare login variables.
$login_postfields = array();
$login_postfields['username'] = $this->username;
$login_postfields['password'] = $this->password;
$login_postfields['realm'] = $this->realm;

$login_postfields_string = http_build_query($login_postfields);
unset($login_postfields);
if(!$this->authToken){

// Perform login request.
$prox_ch = curl_init();
curl_setopt($prox_ch, CURLOPT_URL, "https://{$this->hostname}:{$this->port}/api2/json/access/ticket");
curl_setopt($prox_ch, CURLOPT_POST, true);
curl_setopt($prox_ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($prox_ch, CURLOPT_POSTFIELDS, $login_postfields_string);
curl_setopt($prox_ch, CURLOPT_SSL_VERIFYPEER, $this->verify_ssl);
curl_setopt($prox_ch, CURLOPT_SSL_VERIFYHOST, $this->verify_ssl);
// Prepare login variables.
$login_postfields = array();
$login_postfields['username'] = $this->username;
$login_postfields['password'] = $this->password;
$login_postfields['realm'] = $this->realm;

$login_ticket = curl_exec($prox_ch);
$login_request_info = curl_getinfo($prox_ch);
$login_postfields_string = http_build_query($login_postfields);
unset($login_postfields);

curl_close($prox_ch);
unset($prox_ch);
unset($login_postfields_string);

if (!$login_ticket) {
// SSL negotiation failed or connection timed out
$this->login_ticket_timestamp = null;
return false;
}
// Perform login request.
$prox_ch = curl_init();
curl_setopt($prox_ch, CURLOPT_URL, "https://{$this->hostname}:{$this->port}/api2/json/access/ticket");
curl_setopt($prox_ch, CURLOPT_POST, true);
curl_setopt($prox_ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($prox_ch, CURLOPT_POSTFIELDS, $login_postfields_string);
curl_setopt($prox_ch, CURLOPT_SSL_VERIFYPEER, $this->verify_ssl);
curl_setopt($prox_ch, CURLOPT_SSL_VERIFYHOST, $this->verify_ssl);

$login_ticket_data = json_decode($login_ticket, true);
if ($login_ticket_data == null || $login_ticket_data['data'] == null) {
// Login failed.
// Just to be safe, set this to null again.
$this->login_ticket_timestamp = null;
if ($login_request_info['ssl_verify_result'] == 1) {
throw new PVE2_Exception("Invalid SSL cert on {$this->hostname} - check that the hostname is correct, and that it appears in the server certificate's SAN list. Alternatively set the verify_ssl flag to false if you are using internal self-signed certs (ensure you are aware of the security risks before doing so).", 4);
$login_ticket = curl_exec($prox_ch);
$login_request_info = curl_getinfo($prox_ch);

curl_close($prox_ch);
unset($prox_ch);
unset($login_postfields_string);

if (!$login_ticket) {
// SSL negotiation failed or connection timed out
$this->login_ticket_timestamp = null;
return false;
}
return false;
} else {
// Login success.
$this->login_ticket = $login_ticket_data['data'];
// We store a UNIX timestamp of when the ticket was generated here,
// so we can identify when we need a new one expiration-wise later
// on...
$this->login_ticket_timestamp = time();
$this->reload_node_list();
return true;

$login_ticket_data = json_decode($login_ticket, true);
if ($login_ticket_data == null || $login_ticket_data['data'] == null) {
// Login failed.
// Just to be safe, set this to null again.
$this->login_ticket_timestamp = null;
if ($login_request_info['ssl_verify_result'] == 1) {
throw new PVE2_Exception("Invalid SSL cert on {$this->hostname} - check that the hostname is correct, and that it appears in the server certificate's SAN list. Alternatively set the verify_ssl flag to false if you are using internal self-signed certs (ensure you are aware of the security risks before doing so).", 4);
}
return false;
} else {
// Login success.
$this->login_ticket = $login_ticket_data['data'];
// We store a UNIX timestamp of when the ticket was generated here,
// so we can identify when we need a new one expiration-wise later
// on...
$this->login_ticket_timestamp = time();
$this->reload_node_list();
return true;
}

}else{

$prox_ch = curl_init();
curl_setopt($prox_ch, CURLOPT_URL, "https://{$this->hostname}:{$this->port}/api2/json/version");
curl_setopt($prox_ch, CURLOPT_POST, false);
curl_setopt($prox_ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($prox_ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($prox_ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($prox_ch, CURLOPT_HTTPHEADER, array("Authorization: PVEAPIToken={$this->authToken}")) ;

$login_ticket = curl_exec($prox_ch);
$login_ticket_data = json_decode($login_ticket, true);

if(!$login_ticket_data["data"]["version"]){
return false;
}else{
return true;
}

}


}

# Sets the PVEAuthCookie
Expand All @@ -138,6 +173,7 @@ public function setCookie() {
* Method of checking is purely by age of ticket right now...
*/
protected function check_login_ticket () {

if ($this->login_ticket == null) {
// Just to be safe, set this to null again.
$this->login_ticket_timestamp = null;
Expand All @@ -163,17 +199,29 @@ private function action ($action_path, $http_method, $put_post_parameters = null
if (substr($action_path, 0, 1) != "/") {
$action_path = "/".$action_path;
}


// Prepare cURL resource.
$prox_ch = curl_init();

$put_post_http_headers = array();

if(!$this->authToken){

if (!$this->check_login_ticket()) {
throw new PVE2_Exception("Not logged into Proxmox host. No Login access ticket found or ticket expired.", 3);
}

if (!$this->check_login_ticket()) {
throw new PVE2_Exception("Not logged into Proxmox host. No Login access ticket found or ticket expired.", 3);
$put_post_http_headers[] = "CSRFPreventionToken: {$this->login_ticket['CSRFPreventionToken']}";
curl_setopt($prox_ch, CURLOPT_COOKIE, "PVEAuthCookie=".$this->login_ticket['ticket']);

}else{
$put_post_http_headers[] = "Authorization: PVEAPIToken={$this->authToken}";
}

// Prepare cURL resource.
$prox_ch = curl_init();
// generate API URL
curl_setopt($prox_ch, CURLOPT_URL, "https://{$this->hostname}:{$this->port}/api2/json{$action_path}");

$put_post_http_headers = array();
$put_post_http_headers[] = "CSRFPreventionToken: {$this->login_ticket['CSRFPreventionToken']}";
// Lets decide what type of action we are taking...
switch ($http_method) {
case "GET":
Expand All @@ -186,9 +234,7 @@ private function action ($action_path, $http_method, $put_post_parameters = null
$action_postfields_string = http_build_query($put_post_parameters);
curl_setopt($prox_ch, CURLOPT_POSTFIELDS, $action_postfields_string);
unset($action_postfields_string);

// Add required HTTP headers.
curl_setopt($prox_ch, CURLOPT_HTTPHEADER, $put_post_http_headers);

break;
case "POST":
curl_setopt($prox_ch, CURLOPT_POST, true);
Expand All @@ -213,9 +259,12 @@ private function action ($action_path, $http_method, $put_post_parameters = null
return false;
}

// Add required HTTP headers.
curl_setopt($prox_ch, CURLOPT_HTTPHEADER, $put_post_http_headers);

curl_setopt($prox_ch, CURLOPT_HEADER, true);
curl_setopt($prox_ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($prox_ch, CURLOPT_COOKIE, "PVEAuthCookie=".$this->login_ticket['ticket']);
curl_setopt($prox_ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($prox_ch, CURLOPT_SSL_VERIFYHOST, false);

Expand Down