Add OPNsense certificate activation plugin
This commit is contained in:
parent
bb55b949a3
commit
c685229437
7 changed files with 559 additions and 0 deletions
9
plugin/Makefile
Normal file
9
plugin/Makefile
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
PLUGIN_NAME= certificate-activator
|
||||||
|
PLUGIN_VERSION= 0.1
|
||||||
|
PLUGIN_REVISION= 1
|
||||||
|
PLUGIN_COMMENT= Transactional WebGUI certificate activator
|
||||||
|
PLUGIN_MAINTAINER= [email protected]
|
||||||
|
PLUGIN_LICENSE= BSD2CLAUSE
|
||||||
|
PLUGIN_WWW= https://git.quorate.dev/ms2k/ostension
|
||||||
|
|
||||||
|
.include "../../Mk/plugins.mk"
|
||||||
3
plugin/pkg-descr
Normal file
3
plugin/pkg-descr
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
Adds a narrow API for transactional OPNsense WebGUI certificate activation.
|
||||||
|
An out-of-process watchdog restores the previous certificate when the caller
|
||||||
|
does not confirm the new TLS endpoint before the activation deadline.
|
||||||
5
plugin/src/etc/rc.syshook.d/early/40-opnsense-certificate-activator
Executable file
5
plugin/src/etc/rc.syshook.d/early/40-opnsense-certificate-activator
Executable file
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
if ! output=$(/usr/local/opnsense/scripts/OPNsense/WebGuiCertificate/transaction.php recover 2>&1); then
|
||||||
|
logger -t opnsense-certificate-activator "$output"
|
||||||
|
fi
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace OPNsense\WebGuiCertificate\Api;
|
||||||
|
|
||||||
|
use OPNsense\Base\ApiControllerBase;
|
||||||
|
use OPNsense\Core\Backend;
|
||||||
|
|
||||||
|
class ServiceController extends ApiControllerBase
|
||||||
|
{
|
||||||
|
private const REFERENCE_PATTERN = '/^[0-9a-f]{13}$/D';
|
||||||
|
private const TRANSACTION_PATTERN = '/^[0-9a-f]{32}$/D';
|
||||||
|
|
||||||
|
public function statusAction()
|
||||||
|
{
|
||||||
|
if (!$this->request->isGet()) {
|
||||||
|
return ['status' => 'failed'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->run('webguicertificate status');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function activateAction()
|
||||||
|
{
|
||||||
|
if (!$this->request->isPost()) {
|
||||||
|
return ['status' => 'failed'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$reference = (string)$this->request->getPost('refid', null, '');
|
||||||
|
if (!preg_match(self::REFERENCE_PATTERN, $reference)) {
|
||||||
|
return ['status' => 'failed', 'message' => 'invalid certificate reference'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$backend = new Backend();
|
||||||
|
$transaction = bin2hex(random_bytes(16));
|
||||||
|
$prepared = $this->decode($backend->configdpRun('webguicertificate prepare', [$reference, $transaction]));
|
||||||
|
if (($prepared['status'] ?? '') === 'active') {
|
||||||
|
return $prepared;
|
||||||
|
}
|
||||||
|
if (($prepared['status'] ?? '') !== 'prepared') {
|
||||||
|
return $prepared;
|
||||||
|
}
|
||||||
|
|
||||||
|
$backend->configdpRun('webguicertificate watchdog', [$transaction], true);
|
||||||
|
$deadline = microtime(true) + 5;
|
||||||
|
do {
|
||||||
|
usleep(100000);
|
||||||
|
$status = $this->decode($backend->configdpRun('webguicertificate status'));
|
||||||
|
if (($status['phase'] ?? '') === 'armed' && ($status['transaction'] ?? '') === $transaction) {
|
||||||
|
return $this->decode($backend->configdpRun('webguicertificate activate', [$transaction]));
|
||||||
|
}
|
||||||
|
} while (microtime(true) < $deadline);
|
||||||
|
|
||||||
|
$backend->configdpRun('webguicertificate cancel', [$transaction]);
|
||||||
|
return ['status' => 'failed', 'message' => 'transaction watchdog did not arm'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function confirmAction()
|
||||||
|
{
|
||||||
|
if (!$this->request->isPost()) {
|
||||||
|
return ['status' => 'failed'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$transaction = (string)$this->request->getPost('transaction', null, '');
|
||||||
|
if (!preg_match(self::TRANSACTION_PATTERN, $transaction)) {
|
||||||
|
return ['status' => 'failed', 'message' => 'invalid transaction'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->run('webguicertificate confirm', [$transaction]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function run($action, $parameters = [])
|
||||||
|
{
|
||||||
|
return $this->decode((new Backend())->configdpRun($action, $parameters));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function decode($payload)
|
||||||
|
{
|
||||||
|
$result = json_decode(trim($payload), true);
|
||||||
|
return is_array($result) ? $result : ['status' => 'failed'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<acl>
|
||||||
|
<page-webgui-certificate-activation>
|
||||||
|
<name>System: WebGUI certificate activation</name>
|
||||||
|
<patterns>
|
||||||
|
<pattern>api/webguicertificate/service/status</pattern>
|
||||||
|
<pattern>api/webguicertificate/service/activate</pattern>
|
||||||
|
<pattern>api/webguicertificate/service/confirm</pattern>
|
||||||
|
</patterns>
|
||||||
|
</page-webgui-certificate-activation>
|
||||||
|
</acl>
|
||||||
416
plugin/src/opnsense/scripts/OPNsense/WebGuiCertificate/transaction.php
Executable file
416
plugin/src/opnsense/scripts/OPNsense/WebGuiCertificate/transaction.php
Executable file
|
|
@ -0,0 +1,416 @@
|
||||||
|
#!/usr/local/bin/php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require_once('script/load_phalcon.php');
|
||||||
|
|
||||||
|
use OPNsense\Core\Backend;
|
||||||
|
use OPNsense\Core\Config;
|
||||||
|
|
||||||
|
const MINIMUM_REMAINING_VALIDITY = 604800;
|
||||||
|
const ARM_TTL = 15;
|
||||||
|
const TRANSACTION_TTL = 120;
|
||||||
|
const STATE_VERSION = 1;
|
||||||
|
const STATE_DIRECTORY = '/var/db/opnsense-certificate-activator';
|
||||||
|
const STATE_FILE = STATE_DIRECTORY . '/transaction.json';
|
||||||
|
const LOCK_FILE = STATE_DIRECTORY . '/transaction.lock';
|
||||||
|
|
||||||
|
function result($status, $values = [])
|
||||||
|
{
|
||||||
|
echo json_encode(array_merge(['status' => $status], $values), JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR) . PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openLock()
|
||||||
|
{
|
||||||
|
if (!is_dir(STATE_DIRECTORY) && !mkdir(STATE_DIRECTORY, 0700, true) && !is_dir(STATE_DIRECTORY)) {
|
||||||
|
throw new RuntimeException('unable to create state directory');
|
||||||
|
}
|
||||||
|
|
||||||
|
$lock = fopen(LOCK_FILE, 'c');
|
||||||
|
if ($lock === false || !flock($lock, LOCK_EX)) {
|
||||||
|
throw new RuntimeException('unable to lock transaction state');
|
||||||
|
}
|
||||||
|
if (!chmod(LOCK_FILE, 0600)) {
|
||||||
|
flock($lock, LOCK_UN);
|
||||||
|
fclose($lock);
|
||||||
|
throw new RuntimeException('unable to protect transaction lock');
|
||||||
|
}
|
||||||
|
return $lock;
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeLock($lock)
|
||||||
|
{
|
||||||
|
flock($lock, LOCK_UN);
|
||||||
|
fclose($lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateState($state)
|
||||||
|
{
|
||||||
|
$required = ['version', 'transaction', 'previous_refid', 'candidate_refid', 'phase', 'deadline'];
|
||||||
|
foreach ($required as $key) {
|
||||||
|
if (!array_key_exists($key, $state)) {
|
||||||
|
throw new RuntimeException('invalid transaction state');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($state['version'] !== STATE_VERSION ||
|
||||||
|
!preg_match('/^[0-9a-f]{32}$/D', (string)$state['transaction']) ||
|
||||||
|
!preg_match('/^[0-9a-f]{13}$/D', (string)$state['previous_refid']) ||
|
||||||
|
!preg_match('/^[0-9a-f]{13}$/D', (string)$state['candidate_refid']) ||
|
||||||
|
!in_array($state['phase'], ['prepared', 'armed', 'activating', 'activated'], true) ||
|
||||||
|
!is_int($state['deadline'])) {
|
||||||
|
throw new RuntimeException('invalid transaction state');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function readState()
|
||||||
|
{
|
||||||
|
if (!is_file(STATE_FILE)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$encoded = file_get_contents(STATE_FILE);
|
||||||
|
if ($encoded === false) {
|
||||||
|
throw new RuntimeException('unable to read transaction state');
|
||||||
|
}
|
||||||
|
$state = json_decode($encoded, true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
if (!is_array($state)) {
|
||||||
|
throw new RuntimeException('invalid transaction state');
|
||||||
|
}
|
||||||
|
validateState($state);
|
||||||
|
return $state;
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeState($state)
|
||||||
|
{
|
||||||
|
validateState($state);
|
||||||
|
$temporary = STATE_FILE . '.' . bin2hex(random_bytes(8));
|
||||||
|
$encoded = json_encode($state, JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);
|
||||||
|
if (file_put_contents($temporary, $encoded, LOCK_EX) === false) {
|
||||||
|
throw new RuntimeException('unable to write transaction state');
|
||||||
|
}
|
||||||
|
if (!chmod($temporary, 0600)) {
|
||||||
|
unlink($temporary);
|
||||||
|
throw new RuntimeException('unable to protect transaction state');
|
||||||
|
}
|
||||||
|
if (!rename($temporary, STATE_FILE)) {
|
||||||
|
@unlink($temporary);
|
||||||
|
throw new RuntimeException('unable to publish transaction state');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeState()
|
||||||
|
{
|
||||||
|
if (is_file(STATE_FILE) && !unlink(STATE_FILE)) {
|
||||||
|
throw new RuntimeException('unable to remove transaction state');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function certificateByReference($reference)
|
||||||
|
{
|
||||||
|
foreach (Config::getInstance()->object()->cert as $certificate) {
|
||||||
|
if ((string)$certificate->refid === $reference) {
|
||||||
|
return $certificate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateCandidate($reference)
|
||||||
|
{
|
||||||
|
$certificate = certificateByReference($reference);
|
||||||
|
if ($certificate === null) {
|
||||||
|
throw new InvalidArgumentException('certificate reference does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
$leaf = base64_decode((string)$certificate->crt, true);
|
||||||
|
$privateKey = base64_decode((string)$certificate->prv, true);
|
||||||
|
if ($leaf === false || $privateKey === false || !openssl_x509_check_private_key($leaf, $privateKey)) {
|
||||||
|
throw new InvalidArgumentException('certificate and private key do not match');
|
||||||
|
}
|
||||||
|
|
||||||
|
$details = openssl_x509_parse($leaf);
|
||||||
|
if (!is_array($details)) {
|
||||||
|
throw new InvalidArgumentException('certificate cannot be parsed');
|
||||||
|
}
|
||||||
|
if (($details['validFrom_time_t'] ?? PHP_INT_MAX) > time()) {
|
||||||
|
throw new InvalidArgumentException('certificate is not valid yet');
|
||||||
|
}
|
||||||
|
if (($details['validTo_time_t'] ?? 0) < time() + MINIMUM_REMAINING_VALIDITY) {
|
||||||
|
throw new InvalidArgumentException('certificate validity is too short');
|
||||||
|
}
|
||||||
|
|
||||||
|
$extensions = $details['extensions'] ?? [];
|
||||||
|
$purposes = array_map('trim', explode(',', $extensions['extendedKeyUsage'] ?? ''));
|
||||||
|
if (!in_array('TLS Web Server Authentication', $purposes, true)) {
|
||||||
|
throw new InvalidArgumentException('certificate lacks server authentication usage');
|
||||||
|
}
|
||||||
|
if (isset($extensions['basicConstraints']) && $extensions['basicConstraints'] !== 'CA:FALSE') {
|
||||||
|
throw new InvalidArgumentException('certificate is not a server leaf');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setWebGuiReference($reference, $description)
|
||||||
|
{
|
||||||
|
$config = Config::getInstance();
|
||||||
|
$config->lock();
|
||||||
|
try {
|
||||||
|
if (certificateByReference($reference) === null) {
|
||||||
|
throw new InvalidArgumentException('certificate reference does not exist');
|
||||||
|
}
|
||||||
|
$config->object()->system->webgui->{'ssl-certref'} = $reference;
|
||||||
|
$config->save(['description' => $description]);
|
||||||
|
} finally {
|
||||||
|
$config->unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentWebGuiReference()
|
||||||
|
{
|
||||||
|
return (string)Config::getInstance()->object()->system->webgui->{'ssl-certref'};
|
||||||
|
}
|
||||||
|
|
||||||
|
function restartWebGui()
|
||||||
|
{
|
||||||
|
(new Backend())->configdpRun('webgui restart', ['0'], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function requireTransaction($state, $transaction)
|
||||||
|
{
|
||||||
|
if ($state === null || !hash_equals((string)$state['transaction'], $transaction)) {
|
||||||
|
throw new InvalidArgumentException('transaction is not pending');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepare($reference, $transaction)
|
||||||
|
{
|
||||||
|
if (!preg_match('/^[0-9a-f]{13}$/D', $reference)) {
|
||||||
|
throw new InvalidArgumentException('invalid certificate reference');
|
||||||
|
}
|
||||||
|
if (!preg_match('/^[0-9a-f]{32}$/D', $transaction)) {
|
||||||
|
throw new InvalidArgumentException('invalid transaction');
|
||||||
|
}
|
||||||
|
|
||||||
|
$lock = openLock();
|
||||||
|
try {
|
||||||
|
$existing = readState();
|
||||||
|
if ($existing !== null) {
|
||||||
|
if (in_array($existing['phase'], ['prepared', 'armed'], true) && $existing['deadline'] <= time()) {
|
||||||
|
removeState();
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException('another activation is pending');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
validateCandidate($reference);
|
||||||
|
$previous = currentWebGuiReference();
|
||||||
|
if ($previous === $reference) {
|
||||||
|
result('active', ['refid' => $reference]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (certificateByReference($previous) === null) {
|
||||||
|
throw new RuntimeException('active certificate reference does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
$state = [
|
||||||
|
'version' => STATE_VERSION,
|
||||||
|
'transaction' => $transaction,
|
||||||
|
'previous_refid' => $previous,
|
||||||
|
'candidate_refid' => $reference,
|
||||||
|
'phase' => 'prepared',
|
||||||
|
'deadline' => time() + ARM_TTL,
|
||||||
|
];
|
||||||
|
writeState($state);
|
||||||
|
result('prepared', $state);
|
||||||
|
} finally {
|
||||||
|
closeLock($lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function activate($transaction)
|
||||||
|
{
|
||||||
|
$lock = openLock();
|
||||||
|
try {
|
||||||
|
$state = readState();
|
||||||
|
requireTransaction($state, $transaction);
|
||||||
|
if ($state['phase'] !== 'armed' || $state['deadline'] <= time()) {
|
||||||
|
throw new RuntimeException('transaction watchdog is not armed');
|
||||||
|
}
|
||||||
|
|
||||||
|
$state['phase'] = 'activating';
|
||||||
|
$state['deadline'] = time() + TRANSACTION_TTL;
|
||||||
|
writeState($state);
|
||||||
|
try {
|
||||||
|
setWebGuiReference($state['candidate_refid'], 'Staged transactional WebGUI certificate activation');
|
||||||
|
$state['phase'] = 'activated';
|
||||||
|
writeState($state);
|
||||||
|
} catch (Throwable $error) {
|
||||||
|
setWebGuiReference($state['previous_refid'], 'Recovered failed WebGUI certificate activation');
|
||||||
|
removeState();
|
||||||
|
throw $error;
|
||||||
|
}
|
||||||
|
result('pending', $state);
|
||||||
|
} finally {
|
||||||
|
closeLock($lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirm($transaction)
|
||||||
|
{
|
||||||
|
$lock = openLock();
|
||||||
|
try {
|
||||||
|
$state = readState();
|
||||||
|
requireTransaction($state, $transaction);
|
||||||
|
if ($state['phase'] !== 'activated') {
|
||||||
|
throw new RuntimeException('candidate certificate is not active');
|
||||||
|
}
|
||||||
|
if ($state['deadline'] <= time()) {
|
||||||
|
throw new RuntimeException('transaction confirmation deadline expired');
|
||||||
|
}
|
||||||
|
if (currentWebGuiReference() !== (string)$state['candidate_refid']) {
|
||||||
|
throw new RuntimeException('candidate certificate is not configured');
|
||||||
|
}
|
||||||
|
removeState();
|
||||||
|
result('confirmed', ['refid' => $state['candidate_refid']]);
|
||||||
|
} finally {
|
||||||
|
closeLock($lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancel($transaction)
|
||||||
|
{
|
||||||
|
$lock = openLock();
|
||||||
|
try {
|
||||||
|
$state = readState();
|
||||||
|
requireTransaction($state, $transaction);
|
||||||
|
if (!in_array($state['phase'], ['prepared', 'armed'], true)) {
|
||||||
|
throw new RuntimeException('active transaction cannot be cancelled');
|
||||||
|
}
|
||||||
|
removeState();
|
||||||
|
result('cancelled');
|
||||||
|
} finally {
|
||||||
|
closeLock($lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function rollback($transaction, $restart)
|
||||||
|
{
|
||||||
|
$lock = openLock();
|
||||||
|
try {
|
||||||
|
$state = readState();
|
||||||
|
if ($state === null || !hash_equals((string)$state['transaction'], $transaction)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (in_array($state['phase'], ['activating', 'activated'], true)) {
|
||||||
|
setWebGuiReference($state['previous_refid'], 'Rolled back unconfirmed WebGUI certificate activation');
|
||||||
|
}
|
||||||
|
removeState();
|
||||||
|
} finally {
|
||||||
|
closeLock($lock);
|
||||||
|
}
|
||||||
|
if ($restart) {
|
||||||
|
restartWebGui();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function watchdog($transaction)
|
||||||
|
{
|
||||||
|
$lock = openLock();
|
||||||
|
try {
|
||||||
|
$state = readState();
|
||||||
|
requireTransaction($state, $transaction);
|
||||||
|
if ($state['phase'] !== 'prepared' || $state['deadline'] <= time()) {
|
||||||
|
throw new RuntimeException('transaction cannot be armed');
|
||||||
|
}
|
||||||
|
$state['phase'] = 'armed';
|
||||||
|
$state['deadline'] = time() + ARM_TTL;
|
||||||
|
writeState($state);
|
||||||
|
} finally {
|
||||||
|
closeLock($lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
$restarted = false;
|
||||||
|
while (true) {
|
||||||
|
$lock = openLock();
|
||||||
|
try {
|
||||||
|
$state = readState();
|
||||||
|
if ($state === null || !hash_equals((string)$state['transaction'], $transaction)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$phase = $state['phase'];
|
||||||
|
$remaining = $state['deadline'] - time();
|
||||||
|
} finally {
|
||||||
|
closeLock($lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($phase === 'activated' && !$restarted) {
|
||||||
|
restartWebGui();
|
||||||
|
$restarted = true;
|
||||||
|
}
|
||||||
|
if ($remaining <= 0) {
|
||||||
|
rollback($transaction, in_array($phase, ['activating', 'activated'], true));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
usleep(200000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function status()
|
||||||
|
{
|
||||||
|
$lock = openLock();
|
||||||
|
try {
|
||||||
|
$state = readState();
|
||||||
|
if ($state === null) {
|
||||||
|
result('idle', ['refid' => currentWebGuiReference()]);
|
||||||
|
} else {
|
||||||
|
result($state['deadline'] <= time() ? 'expired' : 'pending', $state);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
closeLock($lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function recover()
|
||||||
|
{
|
||||||
|
$lock = openLock();
|
||||||
|
try {
|
||||||
|
$state = readState();
|
||||||
|
} finally {
|
||||||
|
closeLock($lock);
|
||||||
|
}
|
||||||
|
if ($state !== null) {
|
||||||
|
rollback($state['transaction'], false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$action = $argv[1] ?? '';
|
||||||
|
$argument = $argv[2] ?? '';
|
||||||
|
$secondArgument = $argv[3] ?? '';
|
||||||
|
switch ($action) {
|
||||||
|
case 'status':
|
||||||
|
status();
|
||||||
|
break;
|
||||||
|
case 'prepare':
|
||||||
|
prepare($argument, $secondArgument);
|
||||||
|
break;
|
||||||
|
case 'activate':
|
||||||
|
activate($argument);
|
||||||
|
break;
|
||||||
|
case 'confirm':
|
||||||
|
confirm($argument);
|
||||||
|
break;
|
||||||
|
case 'cancel':
|
||||||
|
cancel($argument);
|
||||||
|
break;
|
||||||
|
case 'watchdog':
|
||||||
|
watchdog($argument);
|
||||||
|
break;
|
||||||
|
case 'recover':
|
||||||
|
recover();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new InvalidArgumentException('unknown action');
|
||||||
|
}
|
||||||
|
} catch (Throwable $error) {
|
||||||
|
result('failed', ['message' => $error->getMessage()]);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
[status]
|
||||||
|
command:/usr/local/opnsense/scripts/OPNsense/WebGuiCertificate/transaction.php
|
||||||
|
parameters:status
|
||||||
|
type:script_output
|
||||||
|
message:Reading WebGUI certificate activation status
|
||||||
|
|
||||||
|
[prepare]
|
||||||
|
command:/usr/local/opnsense/scripts/OPNsense/WebGuiCertificate/transaction.php
|
||||||
|
parameters:prepare %s %s
|
||||||
|
type:script_output
|
||||||
|
message:Preparing a WebGUI certificate transaction
|
||||||
|
|
||||||
|
[activate]
|
||||||
|
command:/usr/local/opnsense/scripts/OPNsense/WebGuiCertificate/transaction.php
|
||||||
|
parameters:activate %s
|
||||||
|
type:script_output
|
||||||
|
message:Activating a WebGUI certificate transaction
|
||||||
|
|
||||||
|
[confirm]
|
||||||
|
command:/usr/local/opnsense/scripts/OPNsense/WebGuiCertificate/transaction.php
|
||||||
|
parameters:confirm %s
|
||||||
|
type:script_output
|
||||||
|
message:Confirming WebGUI certificate activation
|
||||||
|
|
||||||
|
[cancel]
|
||||||
|
command:/usr/local/opnsense/scripts/OPNsense/WebGuiCertificate/transaction.php
|
||||||
|
parameters:cancel %s
|
||||||
|
type:script_output
|
||||||
|
message:Cancelling a WebGUI certificate transaction
|
||||||
|
|
||||||
|
[watchdog]
|
||||||
|
command:/usr/local/opnsense/scripts/OPNsense/WebGuiCertificate/transaction.php
|
||||||
|
parameters:watchdog %s
|
||||||
|
type:script
|
||||||
|
message:Watching WebGUI certificate activation
|
||||||
Loading…
Reference in a new issue