Skip to content

Commit

Permalink
System: High Availability - XMLRPC Client / replace file_get_contents…
Browse files Browse the repository at this point in the history
…() with curl implementation, closes #7561

While here, also offer optional peer tls verification as this is/was disabled by default.
In most cases verification isn't very relevant when using a direct attached neighbor, but if someone has infrastructure in between, extra safeguards are now possible.

With this inplace, allow_url_fopen can safely be disabled on our end (which was the primary goal here).
  • Loading branch information
AdSchellevis committed Dec 24, 2024
1 parent 0bd12b5 commit 3cbea52
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 60 deletions.
53 changes: 29 additions & 24 deletions src/etc/inc/XMLRPC_Client.inc
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ function xmlrpc_execute($method, $params = [], $debug = false)
$client->setCredentials($username, $hasync['password']);
if ($client->query($method, $params)) {
return $client->getResponse();
} else {
throw new Exception($client->error);
}
}
return false;
Expand Down Expand Up @@ -159,50 +161,53 @@ class SimpleXMLRPC_Client
*/
public function setCredentials($username, $password)
{
$this->authHeader = 'Authorization: Basic ' . base64_encode("$username:$password") . "\r\n";
$this->authHeader = 'Authorization: Basic ' . base64_encode("$username:$password");
}

public function query()
{
/* XXX: xmlrpc is legacy, callers always import legacy config */
global $config;
$tls_verify = !empty($config['hasync']) && !empty($config['hasync']['verifypeer']);

// create xmlrpc request object
$args = func_get_args();
$method = array_shift($args);
$request = new IXR_Request($method, $args);
$request_xml = $request->getXml();

// setup http headers
$headers = 'Host: ' . $this->server . "\r\n";
$headers .= "User-Agent: XML_RPC\r\n";
$headers .= "Content-Type: text/xml\r\n";
$headers .= 'Content-Length: ' . $request->getLength() . "\r\n";
$headers = ['Host: ' . $this->server];
$headers[] = "User-Agent: XML_RPC";
$headers[] = "Content-Type: text/xml";
if ($this->authHeader != null) {
$headers .= $this->authHeader;
$headers[] = $this->authHeader;
}

$this->request_send = $headers . $request_xml;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->url);
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
if (!$tls_verify) {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
}
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLINFO_HEADER_OUT, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $request_xml);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$this->response_received = curl_exec($ch);
if ($this->debug) {
echo sprintf(">>> send %d bytes: \n%s\n", strlen($this->request_send), $this->request_send);
print_r(curl_getinfo($ch));
}

// setup a stream context
$context = stream_context_create(array('http' => array(
'method' => "POST",
'header' => $headers,
'content' => $request_xml,
'timeout' => $this->timeout
),
"ssl" => array(
"verify_peer" => false,
"verify_peer_name" => false,
)
));

$this->response_received = @file_get_contents($this->url, false, $context);
if ($this->response_received === false) {
$this->error = 'fetch error. remote host down?';
$this->error = 'fetch error. remote host down? (' . curl_error($ch) . ')';
curl_close($ch);
return false;
}

curl_close($ch);

if ($this->debug) {
echo sprintf(">>> received %d bytes: \n%s\n", strlen($this->response_received), $this->response_received);
}
Expand Down
2 changes: 1 addition & 1 deletion src/etc/rc.subr.d/recover
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ EOF;
$php_ini = <<<EOF
; File generated via recovery
output_buffering = "0"
; allow_url_fopen = "0"
allow_url_fopen = "0"
expose_php = Off
enable_dl = Off
implicit_flush = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@
This should be empty on the backup machine. When an IP address is offered, both web GUI configurations should be equal (port and protocol).
</help>
</field>
<field>
<id>hasync.verifypeer</id>
<label>Verify peer</label>
<type>checkbox</type>
<help>In most cases the target host will be a directly attached neighbor in which case TLS verification can be ignored.</help>
</field>
<field>
<id>hasync.username</id>
<label>Remote System Username</label>
Expand Down
6 changes: 5 additions & 1 deletion src/opnsense/mvc/app/models/OPNsense/Core/Hasync.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<model>
<mount>//hasync</mount>
<migration_prefix>MHA</migration_prefix>
<version>1.0.1</version>
<version>1.0.2</version>
<description>HA sync</description>
<items>
<disablepreempt type="BooleanField">
Expand Down Expand Up @@ -31,6 +31,10 @@
</OptionValues>
</pfsyncversion>
<synchronizetoip type="TextField"/> <!-- XXX: accepts uri or address -->
<verifypeer type="BooleanField">
<Default>0</Default>
<Required>Y</Required>
</verifypeer>
<username type="TextField"/>
<password type="TextField"/> <!-- XXX -->
<syncitems type="JsonKeyValueStoreField">
Expand Down
70 changes: 37 additions & 33 deletions src/opnsense/scripts/system/ha_xmlrpc_exec.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,37 +35,41 @@
$service = $argv[2] ?? '';
$service_id = $argv[3] ?? '';

switch ($action) {
case 'stop':
$result = xmlrpc_execute('opnsense.stop_service', ['service' => $service, 'id' => $service_id]);
echo json_encode(['response' => $result, 'status' => 'ok']);
break;
case 'start':
$result = xmlrpc_execute('opnsense.start_service', ['service' => $service, 'id' => $service_id]);
echo json_encode(['response' => $result, 'status' => 'ok']);
break;
case 'restart':
$result = xmlrpc_execute('opnsense.restart_service', ['service' => $service, 'id' => $service_id]);
echo json_encode(['response' => $result, 'status' => 'ok']);
break;
case 'reload_templates':
xmlrpc_execute('opnsense.configd_reload_all_templates');
echo json_encode(['status' => 'done']);
break;
case 'exec_sync':
configd_run('filter sync');
echo json_encode(['status' => 'done']);
break;
case 'version':
$payload = xmlrpc_execute('opnsense.firmware_version');
if (isset($payload['firmware'])) {
$payload['firmware']['_my_version'] = shell_safe('opnsense-version -v core');
}
echo json_encode(['response' => $payload]);
break;
case 'services':
echo json_encode(['response' => xmlrpc_execute('opnsense.list_services')]);
break;
default:
echo json_encode(['status' => 'error', 'message' => 'usage ha_xmlrpc_exec.php action [service_id]']);
try {
switch ($action) {
case 'stop':
$result = xmlrpc_execute('opnsense.stop_service', ['service' => $service, 'id' => $service_id]);
echo json_encode(['response' => $result, 'status' => 'ok']);
break;
case 'start':
$result = xmlrpc_execute('opnsense.start_service', ['service' => $service, 'id' => $service_id]);
echo json_encode(['response' => $result, 'status' => 'ok']);
break;
case 'restart':
$result = xmlrpc_execute('opnsense.restart_service', ['service' => $service, 'id' => $service_id]);
echo json_encode(['response' => $result, 'status' => 'ok']);
break;
case 'reload_templates':
xmlrpc_execute('opnsense.configd_reload_all_templates');
echo json_encode(['status' => 'done']);
break;
case 'exec_sync':
configd_run('filter sync');
echo json_encode(['status' => 'done']);
break;
case 'version':
$payload = xmlrpc_execute('opnsense.firmware_version');
if (isset($payload['firmware'])) {
$payload['firmware']['_my_version'] = shell_safe('opnsense-version -v core');
}
echo json_encode(['response' => $payload]);
break;
case 'services':
echo json_encode(['response' => xmlrpc_execute('opnsense.list_services')]);
break;
default:
echo json_encode(['status' => 'error', 'message' => 'usage ha_xmlrpc_exec.php action [service_id]']);
}
} catch (Exception $e) {
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}
2 changes: 1 addition & 1 deletion src/opnsense/service/templates/OPNsense/WebGui/php.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
; File generated via configd
output_buffering = "0"
; allow_url_fopen = "0"
allow_url_fopen = "0"
expose_php = Off
enable_dl = Off
implicit_flush = true
Expand Down

1 comment on commit 3cbea52

@fichtner
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lovely, thank you ❤

Please sign in to comment.