mirror of
https://github.com/opnsense/plugins.git
synced 2026-06-03 22:08:11 -04:00
security/acme-client: merge version 1.1
(cherry picked from commit59d7a14600) (cherry picked from commit8179ef255e) (cherry picked from commita3a39bfc20) (cherry picked from commit61c7feb46d) (cherry picked from commit582e2fcb4b) (cherry picked from commitcd5f545b1f) (cherry picked from commitf192911167) (cherry picked from commit1000f6b4dd) (cherry picked from commitb3249c3827) (cherry picked from commit46303731bd) (cherry picked from commit4cc9055d17) (cherry picked from commitb9328060b1)
This commit is contained in:
parent
fbb91a1b3c
commit
5ad3cc93de
19 changed files with 1114 additions and 84 deletions
|
|
@ -1,5 +1,5 @@
|
|||
PLUGIN_NAME= acme-client
|
||||
PLUGIN_VERSION= 1.0
|
||||
PLUGIN_VERSION= 1.1
|
||||
PLUGIN_COMMENT= Let's Encrypt client
|
||||
PLUGIN_MAINTAINER= opnsense@moov.de
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
/**
|
||||
* Copyright (C) 2017 Frank Wall
|
||||
* Copyright (C) 2015 Deciso B.V.
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
namespace OPNsense\AcmeClient;
|
||||
|
||||
/**
|
||||
* Class ActionsController
|
||||
* @package OPNsense\AcmeClient
|
||||
*/
|
||||
class ActionsController extends \OPNsense\Base\IndexController
|
||||
{
|
||||
public function indexAction()
|
||||
{
|
||||
$this->view->title = "Let's Encrypt Restart Actions";
|
||||
// include form definitions
|
||||
$this->view->formDialogAction = $this->getForm("dialogAction");
|
||||
// choose template
|
||||
$this->view->pick('OPNsense/AcmeClient/actions');
|
||||
}
|
||||
}
|
||||
|
|
@ -202,7 +202,7 @@ class AccountsController extends ApiControllerBase
|
|||
$grid = new UIModelGrid($mdlAcme->accounts->account);
|
||||
return $grid->fetchBindRequest(
|
||||
$this->request,
|
||||
array("enabled", "name", "email","accountid"),
|
||||
array("enabled", "name", "email"),
|
||||
"name"
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,209 @@
|
|||
<?php
|
||||
/**
|
||||
* Copyright (C) 2017 Frank Wall
|
||||
* Copyright (C) 2015 Deciso B.V.
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
namespace OPNsense\AcmeClient\Api;
|
||||
|
||||
use \OPNsense\Base\ApiControllerBase;
|
||||
use \OPNsense\AcmeClient\AcmeClient;
|
||||
use \OPNsense\Core\Config;
|
||||
use \OPNsense\Base\UIModelGrid;
|
||||
|
||||
/**
|
||||
* Class ActionsController
|
||||
* @package OPNsense\AcmeClient
|
||||
*/
|
||||
class ActionsController extends ApiControllerBase
|
||||
{
|
||||
/**
|
||||
* Validate and save model after update or insertion.
|
||||
* Use the reference node and tag to rename validation output for a specific
|
||||
* node to a new offset, which makes it easier to reference specific uuids
|
||||
* without having to use them in the frontend descriptions.
|
||||
* @param $mdl model reference
|
||||
* @param $node reference node, to use as relative offset
|
||||
* @param $reference reference for validation output, used to rename the validation output keys
|
||||
* @return array result / validation output
|
||||
*/
|
||||
private function save($mdl, $node = null, $reference = null)
|
||||
{
|
||||
$result = array("result"=>"failed","validations" => array());
|
||||
// perform validation
|
||||
$valMsgs = $mdl->performValidation();
|
||||
foreach ($valMsgs as $field => $msg) {
|
||||
// replace absolute path to attribute for relative one at uuid.
|
||||
if ($node != null) {
|
||||
$fieldnm = str_replace($node->__reference, $reference, $msg->getField());
|
||||
$result["validations"][$fieldnm] = $msg->getMessage();
|
||||
} else {
|
||||
$result["validations"][$msg->getField()] = $msg->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// serialize model to config and save when there are no validation errors
|
||||
if (count($result['validations']) == 0) {
|
||||
// save config if validated correctly
|
||||
$mdl->serializeToConfig();
|
||||
|
||||
Config::getInstance()->save();
|
||||
$result = array("result" => "saved");
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieve action settings or return defaults
|
||||
* @param $uuid item unique id
|
||||
* @return array
|
||||
*/
|
||||
public function getAction($uuid = null)
|
||||
{
|
||||
$mdlAcme = new AcmeClient();
|
||||
if ($uuid != null) {
|
||||
$node = $mdlAcme->getNodeByReference('actions.action.'.$uuid);
|
||||
if ($node != null) {
|
||||
// return node
|
||||
return array("action" => $node->getNodes());
|
||||
}
|
||||
} else {
|
||||
// generate new node, but don't save to disc
|
||||
$node = $mdlAcme->actions->action->add();
|
||||
return array("action" => $node->getNodes());
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* update action with given properties
|
||||
* @param $uuid item unique id
|
||||
* @return array
|
||||
*/
|
||||
public function setAction($uuid)
|
||||
{
|
||||
if ($this->request->isPost() && $this->request->hasPost("action")) {
|
||||
$mdlAcme = new AcmeClient();
|
||||
if ($uuid != null) {
|
||||
$node = $mdlAcme->getNodeByReference('actions.action.'.$uuid);
|
||||
if ($node != null) {
|
||||
$node->setNodes($this->request->getPost("action"));
|
||||
return $this->save($mdlAcme, $node, "action");
|
||||
}
|
||||
}
|
||||
}
|
||||
return array("result"=>"failed");
|
||||
}
|
||||
|
||||
/**
|
||||
* add new action and set with attributes from post
|
||||
* @return array
|
||||
*/
|
||||
public function addAction()
|
||||
{
|
||||
$result = array("result"=>"failed");
|
||||
if ($this->request->isPost() && $this->request->hasPost("action")) {
|
||||
$mdlAcme = new AcmeClient();
|
||||
$node = $mdlAcme->actions->action->Add();
|
||||
$node->setNodes($this->request->getPost("action"));
|
||||
return $this->save($mdlAcme, $node, "action");
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* delete action by uuid
|
||||
* @param $uuid item unique id
|
||||
* @return array status
|
||||
*/
|
||||
public function delAction($uuid)
|
||||
{
|
||||
$result = array("result"=>"failed");
|
||||
if ($this->request->isPost()) {
|
||||
$mdlAcme = new AcmeClient();
|
||||
if ($uuid != null) {
|
||||
if ($mdlAcme->actions->action->del($uuid)) {
|
||||
// if item is removed, serialize to config and save
|
||||
$mdlAcme->serializeToConfig();
|
||||
Config::getInstance()->save();
|
||||
$result['result'] = 'deleted';
|
||||
} else {
|
||||
$result['result'] = 'not found';
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* toggle action by uuid (enable/disable)
|
||||
* @param $uuid item unique id
|
||||
* @param $enabled desired state enabled(1)/disabled(0), leave empty for toggle
|
||||
* @return array status
|
||||
*/
|
||||
public function toggleAction($uuid, $enabled = null)
|
||||
{
|
||||
|
||||
$result = array("result" => "failed");
|
||||
if ($this->request->isPost()) {
|
||||
$mdlAcme = new AcmeClient();
|
||||
if ($uuid != null) {
|
||||
$node = $mdlAcme->getNodeByReference('actions.action.' . $uuid);
|
||||
if ($node != null) {
|
||||
if ($enabled == "0" || $enabled == "1") {
|
||||
$node->enabled = (string)$enabled;
|
||||
} elseif ((string)$node->enabled == "1") {
|
||||
$node->enabled = "0";
|
||||
} else {
|
||||
$node->enabled = "1";
|
||||
}
|
||||
$result['result'] = $node->enabled;
|
||||
// if item has toggled, serialize to config and save
|
||||
$mdlAcme->serializeToConfig();
|
||||
Config::getInstance()->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* search actions
|
||||
* @return array
|
||||
*/
|
||||
public function searchAction()
|
||||
{
|
||||
$this->sessionClose();
|
||||
$mdlAcme = new AcmeClient();
|
||||
$grid = new UIModelGrid($mdlAcme->actions->action);
|
||||
return $grid->fetchBindRequest(
|
||||
$this->request,
|
||||
array("enabled", "name", "description"),
|
||||
"name"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -203,7 +203,7 @@ class CertificatesController extends ApiControllerBase
|
|||
$grid = new UIModelGrid($mdlAcme->certificates->certificate);
|
||||
return $grid->fetchBindRequest(
|
||||
$this->request,
|
||||
array("enabled", "name", "altNames", "description","certificateid"),
|
||||
array("enabled", "name", "altNames", "description"),
|
||||
"name"
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ use \OPNsense\Core\Backend;
|
|||
use \OPNsense\Cron\Cron;
|
||||
use \OPNsense\Core\Config;
|
||||
use \OPNsense\Base\UIModelGrid;
|
||||
use \OPNsense\AcmeClient\AcmeClient;
|
||||
|
||||
/**
|
||||
* Class SettingsController
|
||||
|
|
@ -48,12 +49,10 @@ class SettingsController extends ApiMutableModelControllerBase
|
|||
* create new cron job or return already available one
|
||||
* @return array status action
|
||||
*/
|
||||
public function fetchRBCronAction()
|
||||
public function fetchCronIntegrationAction()
|
||||
{
|
||||
$result = array("result" => "no change");
|
||||
|
||||
// TODO: How to force the system to write-out the cronjob?
|
||||
|
||||
if ($this->request->isPost()) {
|
||||
$mdlAcme = $this->getModel();
|
||||
$backend = new Backend();
|
||||
|
|
@ -81,7 +80,7 @@ class SettingsController extends ApiMutableModelControllerBase
|
|||
// cron item just created.
|
||||
$mdlAcme->serializeToConfig($validateFullModel = false, $disable_validation = true);
|
||||
Config::getInstance()->save();
|
||||
// Regenerate the crontab
|
||||
// Refresh the crontab
|
||||
$backend->configdRun('template reload OPNsense/Cron');
|
||||
$result['result'] = "new";
|
||||
$result['uuid'] = $cron_uuid;
|
||||
|
|
@ -92,11 +91,13 @@ class SettingsController extends ApiMutableModelControllerBase
|
|||
} elseif ((string)$mdlAcme->settings->UpdateCron != "" and
|
||||
((string)$mdlAcme->settings->autoRenewal == "0" or
|
||||
(string)$mdlAcme->settings->enabled == "0")) {
|
||||
// Get UUID, clean existin entry
|
||||
$cron_uuid = (string)$mdlAcme->settings->UpdateCron;
|
||||
$mdlAcme->settings->UpdateCron = null;
|
||||
$mdlCron = new Cron();
|
||||
// Delete the cronjob item
|
||||
if ($mdlCron->jobs->job->del($cron_uuid)) {
|
||||
// if item is removed, serialize to config and save
|
||||
// If item is removed, serialize to config and save
|
||||
$mdlCron->serializeToConfig();
|
||||
$mdlAcme->serializeToConfig($validateFullModel = false, $disable_validation = true);
|
||||
Config::getInstance()->save();
|
||||
|
|
@ -111,4 +112,269 @@ class SettingsController extends ApiMutableModelControllerBase
|
|||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* integrate with HAProxy plugin or return if already done
|
||||
* @return array status action
|
||||
*/
|
||||
public function fetchHAProxyIntegrationAction()
|
||||
{
|
||||
$result = array("result" => "no change");
|
||||
|
||||
if ($this->request->isPost()) {
|
||||
$mdlAcme = $this->getModel();
|
||||
|
||||
// Check if the required plugin is installed
|
||||
if ((string)$mdlAcme->isPluginInstalled('os-haproxy') != "1") {
|
||||
$this->getLogger()->error("LE check: HAProxy plugin is NOT installed, skipping integration");
|
||||
return($result);
|
||||
}
|
||||
|
||||
// Setup only if AcmeClient and HAProxy integration is enabled.
|
||||
// NOTE: We provide HAProxy integration no matter if the HAProxy plugin
|
||||
// is actually enabled or not. This should avoid confusion.
|
||||
if ((string)$mdlAcme->settings->haproxyIntegration == "1" and
|
||||
(string)$mdlAcme->settings->enabled == "1") {
|
||||
$mdlHAProxy = new \OPNsense\HAProxy\HAProxy();
|
||||
$backend = new Backend();
|
||||
|
||||
// Get current status of HAProxy integration by running various checks.
|
||||
$integration_found = false; // Switch to TRUE if something is found.
|
||||
$integration_complete = true; // Switch to FALSE if anything is missing.
|
||||
$integration_changes = false; // Switch to TRUE if config was changes.
|
||||
|
||||
// Check: HAProxy ACL
|
||||
$acl_ref = (string)$mdlAcme->settings->haproxyAclRef;
|
||||
if (!empty($acl_ref)) {
|
||||
$integration_found = true; // We found something.
|
||||
// Make sure the item was not deleted.
|
||||
if ($mdlHAProxy->getByAclID($acl_ref) === null) {
|
||||
$this->getLogger()->error("LE check: HAProxy integration is incomplete: ACL item not found");
|
||||
$integration_complete = false; // Item is broken.
|
||||
}
|
||||
} else {
|
||||
$integration_complete = false; // Item is missing.
|
||||
}
|
||||
|
||||
// Check: HAProxy action
|
||||
$action_ref = (string)$mdlAcme->settings->haproxyActionRef;
|
||||
if (!empty($action_ref)) {
|
||||
$integration_found = true; // We found something.
|
||||
// Make sure the item was not deleted.
|
||||
if ($mdlHAProxy->getByActionID($action_ref) === null) {
|
||||
$this->getLogger()->error("LE check: HAProxy integration is incomplete: action item not found");
|
||||
$integration_complete = false; // Item is broken.
|
||||
}
|
||||
} else {
|
||||
$integration_complete = false; // Item is missing.
|
||||
}
|
||||
|
||||
// Check: HAProxy server
|
||||
$server_ref = (string)$mdlAcme->settings->haproxyServerRef;
|
||||
if (!empty($server_ref)) {
|
||||
$integration_found = true; // We found something.
|
||||
// Make sure the item was not deleted.
|
||||
if ($mdlHAProxy->getByServerID($server_ref) === null) {
|
||||
$this->getLogger()->error("LE check: HAProxy integration is incomplete: server item not found");
|
||||
$integration_complete = false; // Item is broken.
|
||||
}
|
||||
} else {
|
||||
$integration_complete = false; // Item is missing.
|
||||
}
|
||||
|
||||
// Check: HAProxy backend
|
||||
$backend_ref = (string)$mdlAcme->settings->haproxyBackendRef;
|
||||
if (!empty($backend_ref)) {
|
||||
$integration_found = true; // We found something.
|
||||
// Make sure the item was not deleted.
|
||||
if ($mdlHAProxy->getByBackendID($backend_ref) === null) {
|
||||
$this->getLogger()->error("LE check: HAProxy integration is incomplete: backend item not found");
|
||||
$integration_complete = false; // Item is broken.
|
||||
}
|
||||
} else {
|
||||
$integration_complete = false; // Item is missing.
|
||||
}
|
||||
|
||||
// Check if HAProxy integration is already complete.
|
||||
if ($integration_found and $integration_complete) {
|
||||
$this->getLogger()->error("LE check: HAProxy integration is complete");
|
||||
} else {
|
||||
$integration_changes = true;
|
||||
// Check if we need to remove relics of incomplete HAProxy integration.
|
||||
// NOTE: We try to automatically repair a broken HAProxy integration,
|
||||
// although the user may have deleted some items intentionally.
|
||||
// As long as the HAProxy integration is enabled we assume that
|
||||
// this is an error that should *automatically* be fixed.
|
||||
if ($integration_found and !$integration_complete) {
|
||||
// NOTE: We ignore the return value of the del() calls
|
||||
// too keep this as simple as possible.
|
||||
$this->getLogger()->error("LE check: HAProxy integration is incomplete, removing relics");
|
||||
// Remove obsolete backend item
|
||||
if (!empty($backend_ref)) {
|
||||
if ($mdlHAProxy->backends->backend->del($backend_ref)) {
|
||||
$this->getLogger()->error("LE HAProxy integration: deleted obsolete backend item");
|
||||
}
|
||||
}
|
||||
// Remove obsolete server item
|
||||
if (!empty($server_ref)) {
|
||||
if ($mdlHAProxy->servers->server->del($server_ref)) {
|
||||
$this->getLogger()->error("LE HAProxy integration: deleted obsolete server item");
|
||||
}
|
||||
}
|
||||
// Remove obsolete action item
|
||||
if (!empty($action_ref)) {
|
||||
if ($mdlHAProxy->actions->action->del($action_ref)) {
|
||||
$this->getLogger()->error("LE HAProxy integration: deleted obsolete action item");
|
||||
}
|
||||
}
|
||||
// Remove obsolete ACL item
|
||||
if (!empty($acl_ref)) {
|
||||
if ($mdlHAProxy->acls->acl->del($acl_ref)) {
|
||||
$this->getLogger()->error("LE HAProxy integration: deleted obsolete ACL item");
|
||||
}
|
||||
}
|
||||
// TODO: Remove obsolete ACL link from frontends
|
||||
|
||||
// NOTE: We don't clear the settings refs here, because they
|
||||
// will be overwritten later anyway.
|
||||
$result['result'] = "repaired";
|
||||
} else {
|
||||
$this->getLogger()->error("LE check: HAProxy integration initializing");
|
||||
$result['result'] = "new";
|
||||
}
|
||||
|
||||
// Get TCP port for internal acme webserver from config.
|
||||
$acme_port = (string)$mdlAcme->settings->challengePort;
|
||||
|
||||
// Add a new HAProxy ACL
|
||||
$acl_uuid = $mdlHAProxy->newAcl(
|
||||
"find_acme_challenge",
|
||||
"Added by Let's Encrypt plugin",
|
||||
"path_starts_with",
|
||||
"0",
|
||||
"/.well-known/acme-challenge/"
|
||||
);
|
||||
//$this->getLogger()->error("LE acl: ${acl_uuid}");
|
||||
|
||||
// Add a new HAProxy backend
|
||||
$backend_uuid = $mdlHAProxy->newBackend(
|
||||
"1",
|
||||
"acme_challenge_backend",
|
||||
"Added by Let's Encrypt plugin",
|
||||
"http",
|
||||
"source",
|
||||
"",
|
||||
""
|
||||
);
|
||||
//$this->getLogger()->error("LE backend: ${backend_uuid}");
|
||||
|
||||
// Add a new HAProxy action
|
||||
$action_uuid = $mdlHAProxy->newAction(
|
||||
"redirect_acme_challenges",
|
||||
"Added by Let's Encrypt plugin",
|
||||
"if",
|
||||
"",
|
||||
"and",
|
||||
"use_backend",
|
||||
// Use the new backend uuid in field "useBackend"
|
||||
$backend_uuid,
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
""
|
||||
);
|
||||
//$this->getLogger()->error("LE action: ${action_uuid}");
|
||||
// NOTE: This action is linked to frontends.
|
||||
$action_ref = $action_uuid;
|
||||
|
||||
// Add a new HAProxy server
|
||||
$server_uuid = $mdlHAProxy->newServer(
|
||||
"acme_challenge_host",
|
||||
"Added by Let's Encrypt plugin",
|
||||
"127.0.0.1",
|
||||
$acme_port,
|
||||
"active",
|
||||
"0",
|
||||
"0",
|
||||
""
|
||||
);
|
||||
//$this->getLogger()->error("LE server: ${server_uuid}");
|
||||
|
||||
// Update hidden fields to signal that HAProxy integration is complete.
|
||||
$mdlAcme->settings->haproxyAclRef = $acl_uuid;
|
||||
$mdlAcme->settings->haproxyActionRef = $action_uuid;
|
||||
$mdlAcme->settings->haproxyServerRef = $server_uuid;
|
||||
$mdlAcme->settings->haproxyBackendRef = $backend_uuid;
|
||||
|
||||
// Link new ACL to HAProxy action
|
||||
$link_acl_result = $mdlHAProxy->linkAclToAction($acl_uuid,$action_uuid);
|
||||
//$this->getLogger()->error("LE link acl result: ${link_acl_result}");
|
||||
|
||||
// Link new server to HAProxy backend
|
||||
$link_server_result = $mdlHAProxy->linkServerToBackend($server_uuid,$backend_uuid);
|
||||
//$this->getLogger()->error("LE link server result: ${link_server_result}");
|
||||
}
|
||||
|
||||
// Ensure HAProxy frontend additions have been applied.
|
||||
foreach ($mdlAcme->getNodeByReference('validations.validation')->__items as $validation) {
|
||||
// Find all (enabled) validation methods with HAProxy integration.
|
||||
if ((string)$validation->enabled == "1" and
|
||||
(string)$validation->method == "http01" and
|
||||
(string)$validation->http_service == "haproxy") {
|
||||
//$this->getLogger()->error("LE HAProxy DEBUG: checking validation method: " . (string)$validation->name);
|
||||
$_frontends = explode(',', $validation->http_haproxyFrontends);
|
||||
// Walk through all linked frontends.
|
||||
foreach ($_frontends as $_frontend) {
|
||||
//$this->getLogger()->error("LE HAProxy DEBUG: checking frontend: ${_frontend}");
|
||||
$frontend = $mdlHAProxy->getByFrontendID($_frontend);
|
||||
// Make sure the frontend was found in config.
|
||||
if (!empty((string)$frontend->id)) {
|
||||
// Check if the HAProxy ACME Action is linked to this frontend.
|
||||
$_actions = $frontend->linkedActions;
|
||||
if (strpos($_actions,$action_ref) !== false) {
|
||||
// Match! Nothing to do.
|
||||
} else {
|
||||
// Link to ACME Action is currently missing: add it!
|
||||
if (!empty((string)$_actions)) {
|
||||
// Extend existing string.
|
||||
$_actions .= ",${action_ref}";
|
||||
} else {
|
||||
// First linked Action for this frontend.
|
||||
$_actions = $action_ref;
|
||||
}
|
||||
// Add modified list of linked Actions to frontend.
|
||||
$frontend->linkedActions = $_actions;
|
||||
$this->getLogger()->error("LE HAProxy integration: updating frontend ${_frontend}");
|
||||
// We need to write changes to config.
|
||||
$integration_changes = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Changes made to configuration?
|
||||
if ($integration_changes === true) {
|
||||
$this->getLogger()->error("LE HAProxy integration: saving updated configuration");
|
||||
// Save updated configuration.
|
||||
// Do NOT validate because the current in-memory model doesn't know about the
|
||||
// HAProxy items just created.
|
||||
// FIXME: works, but still leads to "Related item not found" errors in the log file
|
||||
$mdlHAProxy->serializeToConfig($validateFullModel = false, $disable_validation = true);
|
||||
$mdlAcme->serializeToConfig($validateFullModel = false, $disable_validation = true);
|
||||
Config::getInstance()->save();
|
||||
|
||||
// Reconfigure HAProxy
|
||||
$backend->configdRun('template reload OPNsense/HAProxy');
|
||||
$response = $backend->configdRun("haproxy restart");
|
||||
}
|
||||
|
||||
} else {
|
||||
// NOTE: HAProxy integration is NOT removed if the user disables it, because
|
||||
// we might destroy changes made by the user when doing so.
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ class ValidationsController extends ApiControllerBase
|
|||
$grid = new UIModelGrid($mdlAcme->validations->validation);
|
||||
return $grid->fetchBindRequest(
|
||||
$this->request,
|
||||
array("enabled", "name", "description","validationid"),
|
||||
array("enabled", "name", "description"),
|
||||
"name"
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
<form>
|
||||
<field>
|
||||
<id>action.enabled</id>
|
||||
<label>Enabled</label>
|
||||
<type>checkbox</type>
|
||||
<help>Enable this restart action.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>action.name</id>
|
||||
<label>Name</label>
|
||||
<type>text</type>
|
||||
<help>Name to identify this restart action.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>action.description</id>
|
||||
<label>Description</label>
|
||||
<type>text</type>
|
||||
<help>Description for this restart action.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>action.type</id>
|
||||
<label>Restart Command</label>
|
||||
<type>dropdown</type>
|
||||
<help>Pre-defined commands for this restart action.</help>
|
||||
</field>
|
||||
<field>
|
||||
<label>Optional Parameters</label>
|
||||
<type>header</type>
|
||||
</field>
|
||||
<field>
|
||||
<id>action.configd</id>
|
||||
<label>System Command</label>
|
||||
<type>dropdown</type>
|
||||
<help>Select a pre-defined system command which should be run for this action.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>action.custom</id>
|
||||
<label>Custom Command</label>
|
||||
<type>textbox</type>
|
||||
<help>Specify a custom commands which should be run for this action.</help>
|
||||
</field>
|
||||
</form>
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
<type>select_multiple</type>
|
||||
<style>tokenize</style>
|
||||
<allownew>true</allownew>
|
||||
<help><![CDATA[Configure additional names that should be part pf the certificate, i.e. www.example.com or mail.example.com. Use TAB key to complete typing a FQDN.<br/><div class="text-info"><b>NOTE:</b>Cannot be altered once the certificate was signed by the Let's Encrypt Authority! You need to create a new certificate to add additional names.</div>]]></help>
|
||||
<help><![CDATA[Configure additional names that should be part pf the certificate, i.e. www.example.com or mail.example.com. Use TAB key to complete typing a FQDN.<br/><div class="text-info"><b>NOTE:</b>You need to forcefully re-issue the certificate if you change "Alt Names" after the certificate was signed by the Let's Encrypt Authority! Use the "issue" button in the Commands column in this case.</div>]]></help>
|
||||
<hint>Enter FQDN here. Finish with TAB.</hint>
|
||||
</field>
|
||||
<field>
|
||||
|
|
@ -38,6 +38,14 @@
|
|||
<type>dropdown</type>
|
||||
<help><![CDATA[Set the Let's Encrypt validation method for this certificate.]]></help>
|
||||
</field>
|
||||
<field>
|
||||
<id>certificate.restartActions</id>
|
||||
<label>Restart Actions</label>
|
||||
<type>select_multiple</type>
|
||||
<style>tokenize</style>
|
||||
<allownew>true</allownew>
|
||||
<help>Choose the actions that should be run after certificate renewal. Basically every application requires a quick restart to reload the updated certificate. If you don't configure a restart action, the in-memory certificate may expire and cause security warnings and other issues.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>certificate.autoRenewal</id>
|
||||
<label>Auto Renewal</label>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
<id>validation.method</id>
|
||||
<label>Validation Method</label>
|
||||
<type>dropdown</type>
|
||||
<help><![CDATA[Set the Let's Encrypt validation method.]]></help>
|
||||
<help><![CDATA[Set the Let's Encrypt validation method. You'll have to add configuration for the selected method below.]]></help>
|
||||
</field>
|
||||
<field>
|
||||
<label>HTTP-01</label>
|
||||
|
|
@ -41,7 +41,7 @@
|
|||
<id>validation.http_opn_autodiscovery</id>
|
||||
<label>IP Auto-Discovery</label>
|
||||
<type>checkbox</type>
|
||||
<help><![CDATA[The FQDN's used in your certificate must currently point to an official IP address. Choose this option to let OPNsense tryo to auto-discover these IP addresses. This will lead to a short downtime of the service that is normally used with this IP address.<br/><div class="text-info"><b>NOTE:</b>This will ONLY work if the official IP addresses are LOCALLY configured on your OPNsense firewall.</div>]]></help>
|
||||
<help><![CDATA[The FQDN's used in your certificate must currently point to an official IP address. Choose this option to let OPNsense try to auto-discover these IP addresses. This will lead to a short downtime of the service that is normally used with this IP address.<br/><div class="text-info"><b>NOTE:</b>This will ONLY work if the official IP addresses are LOCALLY configured on your OPNsense firewall.</div>]]></help>
|
||||
</field>
|
||||
<field>
|
||||
<id>validation.http_opn_interface</id>
|
||||
|
|
@ -58,19 +58,25 @@
|
|||
<help><![CDATA[The FQDN's used in your certificate must currently point to one or more official IP addresses. Enter the all of these IP addresses here. OPNsense will automatically create a temporary port forward to allow the Let's Encrypt validation to succeed. This will lead to a short downtime of the service that is normally used with these IP addresses.<br/><div class="text-info"><b>NOTE:</b>This will ONLY work if the official IP addresses are LOCALLY configured on your OPNsense firewall.</div>]]></help>
|
||||
<hint>Enter IP addresses here. Finish each with TAB.</hint>
|
||||
</field>
|
||||
<!--
|
||||
<field>
|
||||
<label>HTTP-01/HAProxy</label>
|
||||
<type>header</type>
|
||||
</field>
|
||||
<field>
|
||||
<id>validation.http_haproxyInject</id>
|
||||
<label>HAProxy Config Injection</label>
|
||||
<label>Enable Auto-Configuration</label>
|
||||
<type>checkbox</type>
|
||||
<help>Automatically inject config into the local HAProxy instance to let it serve acme challanges without service interruption.</help>
|
||||
<help>Automatically inject config into the local HAProxy instance to let it serve acme challanges without service interruption. Of course, adding the configuration requires a short restart of the HAProxy service.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>validation.http_haproxyFrontend</id>
|
||||
<label>HAProxy Frontend</label>
|
||||
<type>dropdown</type>
|
||||
<help>Choose the local HAProxy frontend that should be configured to server acme challenges.</help>
|
||||
<id>validation.http_haproxyFrontends</id>
|
||||
<label>HAProxy Frontends</label>
|
||||
<type>select_multiple</type>
|
||||
<style>tokenize</style>
|
||||
<allownew>true</allownew>
|
||||
<help>Choose the local HAProxy frontends. They will automatically be configured to redirect acme challenges to the internal acme client. The HAProxy service will automatically be restarted if a certificate was renewed.</help>
|
||||
</field>
|
||||
<!--
|
||||
<field>
|
||||
<id>validation.http_relaydInject</id>
|
||||
<label>Loadbalancer Config Injection</label>
|
||||
|
|
@ -98,7 +104,7 @@
|
|||
<id>validation.dns_sleep</id>
|
||||
<label>Sleep Time</label>
|
||||
<type>text</type>
|
||||
<help><![CDATA[The time in seconds to wait for all the TXT records to take effect DNS API mode. Default 120 seconds.]]></help>
|
||||
<help><![CDATA[The time in seconds to wait for all the TXT records to take effect after adding them to the DNS API. Defaults to 120 seconds.]]></help>
|
||||
</field>
|
||||
<field>
|
||||
<label>DNS-01/Alwaysdata</label>
|
||||
|
|
@ -231,7 +237,7 @@
|
|||
<field>
|
||||
<id>validation.dns_ispconfig_insecure</id>
|
||||
<label>Disable SSL Verification</label>
|
||||
<type>text</type>
|
||||
<type>checkbox</type>
|
||||
<help></help>
|
||||
</field>
|
||||
<field>
|
||||
|
|
@ -302,7 +308,7 @@
|
|||
<id>validation.dns_nsupdate_key</id>
|
||||
<label>Secret Key</label>
|
||||
<type>textbox</type>
|
||||
<help></help>
|
||||
<help><![CDATA[Requires the the whole key file in a format that is compatible with nsupdate.]]></help>
|
||||
</field>
|
||||
<field>
|
||||
<label>DNS-01/OVH</label>
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@
|
|||
<id>acmeclient.settings.enabled</id>
|
||||
<label>Enable Plugin</label>
|
||||
<type>checkbox</type>
|
||||
<help><![CDATA[Enable Let's Encrypt plugin]]></help>
|
||||
<help><![CDATA[Enable Let's Encrypt plugin.]]></help>
|
||||
</field>
|
||||
<field>
|
||||
<id>acmeclient.settings.autoRenewal</id>
|
||||
<label>Auto Renewal</label>
|
||||
<type>checkbox</type>
|
||||
<help><![CDATA[Enable automatic renewal for certificates to prevent expiration.]]></help>
|
||||
<help><![CDATA[Enable automatic renewal for certificates to prevent expiration. This will add a cronjob to the system. You may want to customize the cronjob schedule to your needs, because re-issueing a certificate may lead to a short downtime, depending on the selected validation method and service.]]></help>
|
||||
</field>
|
||||
<field>
|
||||
<id>acmeclient.settings.environment</id>
|
||||
|
|
@ -17,6 +17,12 @@
|
|||
<type>dropdown</type>
|
||||
<help><![CDATA[Choose Let's Encrypts staging environment when using it for the first time or while testing new validation methods. The staging environment offers <a href="https://letsencrypt.org/docs/staging-environment/">relaxed rate limits</a>.<br/><div class="text-info"><b>NOTE:</b>Certificates signed by the staging environment are NOT valid. You need to forcefully re-sign (or delete and re-create) them after switching from staging to production environment.</div>]]></help>
|
||||
</field>
|
||||
<field>
|
||||
<id>acmeclient.settings.haproxyIntegration</id>
|
||||
<label>HAProxy Integration</label>
|
||||
<type>checkbox</type>
|
||||
<help><![CDATA[Enable automatic integration with the OPNsense HAProxy plugin. <b>Requires that the OPNsense HAProxy plugin is installed.</b> This will automatically add the required backend, server, action and ACL for you. You just need to select your HAProxy frontend when configuration the certificate or validation method. <div class="text-info"><b>NOTE:</b>This will only work for HTTP-01 validation and HAProxy frontends running in <i>http</i> mode; TCP frontends are not supported.</div>]]></help>
|
||||
</field>
|
||||
<field>
|
||||
<id>acmeclient.settings.challengePort</id>
|
||||
<label>Local HTTP Port</label>
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
namespace OPNsense\AcmeClient;
|
||||
|
||||
use OPNsense\Base\BaseModel;
|
||||
use OPNsense\Core\Backend;
|
||||
|
||||
/**
|
||||
* Class AcmeClient
|
||||
|
|
@ -54,15 +55,86 @@ class AcmeClient extends BaseModel
|
|||
|
||||
/**
|
||||
* check if module is enabled
|
||||
* @return bool is the AcmeClient enabled (1 or more active certificates)
|
||||
* @param $checkCertificates bool enable in-depth check (1 or more active certificates)
|
||||
* @return bool is the AcmeClient service enabled
|
||||
*/
|
||||
public function isEnabled()
|
||||
public function isEnabled($checkCertificates=false)
|
||||
{
|
||||
foreach ($this->certificates->certificate->__items as $certificate) {
|
||||
if ((string)$certificate->enabled == "1") {
|
||||
return true;
|
||||
if ((string)$this->settings->enabled === "1") {
|
||||
if ($checkCertificates === true) {
|
||||
foreach ($this->certificates->certificate->__items as $certificate) {
|
||||
if ((string)$certificate->enabled == "1") {
|
||||
return true; // Found a active certificate
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return true; // AcmeClient enabled
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieve restart action by number
|
||||
* @param $uuid action number
|
||||
* @return null|BaseField action details
|
||||
*/
|
||||
public function getByActionID($uuid)
|
||||
{
|
||||
foreach ($this->actions->action->__items as $action) {
|
||||
if ((string)$uuid === (string)$action->getAttributes()["uuid"]) {
|
||||
return $action;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if the specfied plugin is installed
|
||||
* @param $name plugin/package name
|
||||
* @return bool is the plugin installed
|
||||
*/
|
||||
public function isPluginInstalled($name)
|
||||
{
|
||||
// NOTE: Based on infoAction() from Core/Api/FirmwareController.php
|
||||
// FIXME: Should be replaced by a Core function sooner or later.
|
||||
|
||||
$backend = new Backend();
|
||||
$keys = array('name', 'version', 'comment', 'flatsize', 'locked', 'license');
|
||||
$plugins = array();
|
||||
|
||||
// Only check local package data for performance reasons
|
||||
$current = $backend->configdRun("firmware local");
|
||||
$current = explode("\n", trim($current));
|
||||
|
||||
foreach ($current as $line) {
|
||||
/* package infos are flat lists with 3 pipes as delimiter */
|
||||
$expanded = explode('|||', $line);
|
||||
$translated = array();
|
||||
$index = 0;
|
||||
if (count($expanded) != count($keys)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($keys as $key) {
|
||||
$translated[$key] = $expanded[$index++];
|
||||
}
|
||||
|
||||
/* mark local packages as "installed" */
|
||||
$translated['installed'] = "1";
|
||||
|
||||
/* figure out local and remote plugins */
|
||||
$plugin = explode('-', $translated['name']);
|
||||
if (count($plugin)) {
|
||||
if ($plugin[0] == 'os' || $plugin[0] == 'ospriv') {
|
||||
$plugins[$translated['name']] = $translated;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($plugins[$name]) and $plugins[$name]['installed'] == "1") {
|
||||
return 1; // TRUE, is installed
|
||||
} else {
|
||||
return 0; // FALSE, is not installed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,58 @@
|
|||
<MaximumValue>65535</MaximumValue>
|
||||
<Required>Y</Required>
|
||||
</challengePort>
|
||||
<haproxyIntegration type="BooleanField">
|
||||
<default>0</default>
|
||||
<Required>N</Required>
|
||||
</haproxyIntegration>
|
||||
<haproxyAclRef type="ModelRelationField">
|
||||
<Model>
|
||||
<acls>
|
||||
<source>OPNsense.HAProxy.HAProxy</source>
|
||||
<items>acls.acl</items>
|
||||
<display>name</display>
|
||||
</acls>
|
||||
</Model>
|
||||
<ValidationMessage>Related HAProxy ACL not found.</ValidationMessage>
|
||||
<multiple>N</multiple>
|
||||
<Required>N</Required>
|
||||
</haproxyAclRef>
|
||||
<haproxyActionRef type="ModelRelationField">
|
||||
<Model>
|
||||
<actions>
|
||||
<source>OPNsense.HAProxy.HAProxy</source>
|
||||
<items>actions.action</items>
|
||||
<display>name</display>
|
||||
</actions>
|
||||
</Model>
|
||||
<ValidationMessage>Related HAProxy action not found.</ValidationMessage>
|
||||
<multiple>N</multiple>
|
||||
<Required>N</Required>
|
||||
</haproxyActionRef>
|
||||
<haproxyServerRef type="ModelRelationField">
|
||||
<Model>
|
||||
<servers>
|
||||
<source>OPNsense.HAProxy.HAProxy</source>
|
||||
<items>servers.server</items>
|
||||
<display>name</display>
|
||||
</servers>
|
||||
</Model>
|
||||
<ValidationMessage>Related HAProxy server not found.</ValidationMessage>
|
||||
<multiple>N</multiple>
|
||||
<Required>N</Required>
|
||||
</haproxyServerRef>
|
||||
<haproxyBackendRef type="ModelRelationField">
|
||||
<Model>
|
||||
<backends>
|
||||
<source>OPNsense.HAProxy.HAProxy</source>
|
||||
<items>backends.backend</items>
|
||||
<display>name</display>
|
||||
</backends>
|
||||
</Model>
|
||||
<ValidationMessage>Related HAProxy backend not found.</ValidationMessage>
|
||||
<multiple>N</multiple>
|
||||
<Required>N</Required>
|
||||
</haproxyBackendRef>
|
||||
</settings>
|
||||
<accounts>
|
||||
<account type="ArrayField">
|
||||
|
|
@ -55,12 +107,12 @@
|
|||
</enabled>
|
||||
<name type="TextField">
|
||||
<Required>Y</Required>
|
||||
<mask>/^([0-9a-zA-Z._]){1,255}$/u</mask>
|
||||
<mask>/^.{1,255}$/u</mask>
|
||||
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
|
||||
</name>
|
||||
<description type="TextField">
|
||||
<Required>N</Required>
|
||||
<mask>/^([\t\n\v\f\r 0-9a-zA-Z.:\-,_()\x{00A0}-\x{FFFF}]){1,255}$/u</mask>
|
||||
<mask>/^.{1,255}$/u</mask>
|
||||
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
|
||||
</description>
|
||||
<email type="EmailField">
|
||||
|
|
@ -94,29 +146,31 @@
|
|||
</enabled>
|
||||
<name type="TextField">
|
||||
<Required>Y</Required>
|
||||
<mask>/^([0-9a-zA-Z._]){1,255}$/u</mask>
|
||||
<mask>/^.{1,255}$/u</mask>
|
||||
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
|
||||
</name>
|
||||
<description type="TextField">
|
||||
<Required>N</Required>
|
||||
<mask>/^([\t\n\v\f\r 0-9a-zA-Z.:\-,_()\x{00A0}-\x{FFFF}]){1,255}$/u</mask>
|
||||
<mask>/^.{1,255}$/u</mask>
|
||||
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
|
||||
</description>
|
||||
<altNames type="CSVListField">
|
||||
<Required>N</Required>
|
||||
<multiple>Y</multiple>
|
||||
<!--- XXX: FQDN should at least contain one dot -->
|
||||
<mask>/^((([0-9a-zA-Z._\-\*]+\.[0-9a-zA-Z._\-\*]+(-[0-9]+)?)([,]){0,1}))*/u</mask>
|
||||
<mask>/^.{1,16384}$/u</mask>
|
||||
<ChangeCase>lower</ChangeCase>
|
||||
<ValidationMessage>Please provide a valid FQDN, i.e. www.example.com or mail.example.com.</ValidationMessage>
|
||||
<ValidationMessage>Please provide a valid FQDN, i.e. www.example.com or mail.example.com. Field length is limited to 16384 characters.</ValidationMessage>
|
||||
</altNames>
|
||||
<account type="ModelRelationField">
|
||||
<Model>
|
||||
<template>
|
||||
<accounts>
|
||||
<source>OPNsense.AcmeClient.AcmeClient</source>
|
||||
<items>accounts.account</items>
|
||||
<display>name</display>
|
||||
</template>
|
||||
<filters>
|
||||
<enabled>/^(?!0).*$/</enabled>
|
||||
</filters>
|
||||
</accounts>
|
||||
</Model>
|
||||
<ValidationMessage>Related item not found</ValidationMessage>
|
||||
<multiple>N</multiple>
|
||||
|
|
@ -124,16 +178,34 @@
|
|||
</account>
|
||||
<validationMethod type="ModelRelationField">
|
||||
<Model>
|
||||
<template>
|
||||
<validations>
|
||||
<source>OPNsense.AcmeClient.AcmeClient</source>
|
||||
<items>validations.validation</items>
|
||||
<display>name</display>
|
||||
</template>
|
||||
<filters>
|
||||
<enabled>/^(?!0).*$/</enabled>
|
||||
</filters>
|
||||
</validations>
|
||||
</Model>
|
||||
<ValidationMessage>Related item not found</ValidationMessage>
|
||||
<multiple>N</multiple>
|
||||
<Required>Y</Required>
|
||||
</validationMethod>
|
||||
<restartActions type="ModelRelationField">
|
||||
<Model>
|
||||
<actions>
|
||||
<source>OPNsense.AcmeClient.AcmeClient</source>
|
||||
<items>actions.action</items>
|
||||
<display>name</display>
|
||||
<filters>
|
||||
<enabled>/^(?!0).*$/</enabled>
|
||||
</filters>
|
||||
</actions>
|
||||
</Model>
|
||||
<ValidationMessage>Related restart action not found</ValidationMessage>
|
||||
<multiple>Y</multiple>
|
||||
<Required>N</Required>
|
||||
</restartActions>
|
||||
<autoRenewal type="BooleanField">
|
||||
<default>1</default>
|
||||
<Required>Y</Required>
|
||||
|
|
@ -165,12 +237,12 @@
|
|||
</enabled>
|
||||
<name type="TextField">
|
||||
<Required>Y</Required>
|
||||
<mask>/^([0-9a-zA-Z._]){1,255}$/u</mask>
|
||||
<mask>/^.{1,255}$/u</mask>
|
||||
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
|
||||
</name>
|
||||
<description type="TextField">
|
||||
<Required>N</Required>
|
||||
<mask>/^([\t\n\v\f\r 0-9a-zA-Z.:\-,_()\x{00A0}-\x{FFFF}]){1,255}$/u</mask>
|
||||
<mask>/^.{1,255}$/u</mask>
|
||||
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
|
||||
</description>
|
||||
<method type="OptionField">
|
||||
|
|
@ -186,10 +258,7 @@
|
|||
<default>opnsense</default>
|
||||
<OptionValues>
|
||||
<opnsense>OPNsense port forward (specify Interface or IP)</opnsense>
|
||||
<!-- WIP/TODO
|
||||
<haproxy>HAProxy Frontend (OPNsense plugin)</haproxy>
|
||||
<relayd>relayd Loadbalancer Virtual Server (OPNsense plugin)</relayd>
|
||||
-->
|
||||
<haproxy>HAProxy HTTP Frontend Integration (OPNsense plugin)</haproxy>
|
||||
</OptionValues>
|
||||
</http_service>
|
||||
<http_opn_autodiscovery type="BooleanField">
|
||||
|
|
@ -208,32 +277,26 @@
|
|||
<Required>N</Required>
|
||||
<multiple>Y</multiple>
|
||||
</http_opn_ipaddresses>
|
||||
<!-- WIP/TODO
|
||||
<http_haproxyInject type="BooleanField">
|
||||
<default>1</default>
|
||||
<Required>N</Required>
|
||||
</http_haproxyInject>
|
||||
<http_haproxyFrontend type="ModelRelationField">
|
||||
<http_haproxyFrontends type="ModelRelationField">
|
||||
<Model>
|
||||
<template>
|
||||
<frontends>
|
||||
<source>OPNsense.HAProxy.HAProxy</source>
|
||||
<items>frontends.frontend</items>
|
||||
<display>name</display>
|
||||
</template>
|
||||
<filters>
|
||||
<mode>/^(http|ssl)$/</mode>
|
||||
<enabled>/^(?!0).*$/</enabled>
|
||||
</filters>
|
||||
</frontends>
|
||||
</Model>
|
||||
<ValidationMessage>Related item not found</ValidationMessage>
|
||||
<multiple>N</multiple>
|
||||
<ValidationMessage>Related HAProxy frontend not found</ValidationMessage>
|
||||
<multiple>Y</multiple>
|
||||
<Required>N</Required>
|
||||
</http_haproxyFrontend>
|
||||
<http_relaydInject type="BooleanField">
|
||||
<default>1</default>
|
||||
<Required>N</Required>
|
||||
</http_relaydInject>
|
||||
<http_relaydVserver type="TextField">
|
||||
<Required>N</Required>
|
||||
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
|
||||
</http_relaydVserver>
|
||||
-->
|
||||
</http_haproxyFrontends>
|
||||
<dns_service type="OptionField">
|
||||
<Required>Y</Required>
|
||||
<default>dns_nsupdate</default>
|
||||
|
|
@ -369,5 +432,45 @@
|
|||
</dns_pdns_token>
|
||||
</validation>
|
||||
</validations>
|
||||
<actions>
|
||||
<action type="ArrayField">
|
||||
<id type="UniqueIdField">
|
||||
<Required>N</Required>
|
||||
</id>
|
||||
<enabled type="BooleanField">
|
||||
<default>1</default>
|
||||
<Required>Y</Required>
|
||||
</enabled>
|
||||
<name type="TextField">
|
||||
<Required>Y</Required>
|
||||
<mask>/^.{1,255}$/u</mask>
|
||||
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
|
||||
</name>
|
||||
<description type="TextField">
|
||||
<Required>N</Required>
|
||||
<mask>/^.{1,255}$/u</mask>
|
||||
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
|
||||
</description>
|
||||
<type type="OptionField">
|
||||
<Required>Y</Required>
|
||||
<OptionValues>
|
||||
<restart_gui>Restart OPNsense Web UI</restart_gui>
|
||||
<restart_haproxy>Restart HAProxy (OPNsense plugin)</restart_haproxy>
|
||||
<configd>System or Plugin Command (select below)</configd>
|
||||
<custom>Custom Command (specify below)</custom>
|
||||
</OptionValues>
|
||||
</type>
|
||||
<configd type="ConfigdActionsField">
|
||||
<filters>
|
||||
<description>/^(?!.*(Let\'s\ Encrypt|acme|[fF]irmware))([\S\s]{1,255})/</description>
|
||||
</filters>
|
||||
<ValidationMessage>Select a command from the list.</ValidationMessage>
|
||||
<Required>N</Required>
|
||||
</configd>
|
||||
<custom type="TextField">
|
||||
<Required>N</Required>
|
||||
</custom>
|
||||
</action>
|
||||
</actions>
|
||||
</items>
|
||||
</model>
|
||||
|
|
|
|||
|
|
@ -9,9 +9,11 @@
|
|||
</Accounts>
|
||||
<Validations VisibleName="Validation Methods" order="30" url="/ui/acmeclient/validations/">
|
||||
</Validations>
|
||||
<Certificates order="40" url="/ui/acmeclient/certificates/">
|
||||
<Actions VisibleName="Restart Actions" order="40" url="/ui/acmeclient/actions/">
|
||||
</Actions>
|
||||
<Certificates order="50" url="/ui/acmeclient/certificates/">
|
||||
</Certificates>
|
||||
<Log VisibleName="Log File" order="50" url="/diag_logs_acmeclient.php"/>
|
||||
<Log VisibleName="Log File" order="60" url="/diag_logs_acmeclient.php"/>
|
||||
</LEAcmeClient>
|
||||
</Services>
|
||||
</menu>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
{#
|
||||
|
||||
Copyright (C) 2017 Frank Wall
|
||||
OPNsense® is Copyright © 2014-2015 by Deciso B.V.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#}
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
$( document ).ready(function() {
|
||||
|
||||
/***********************************************************************
|
||||
* link grid actions
|
||||
**********************************************************************/
|
||||
|
||||
$("#grid-actions").UIBootgrid(
|
||||
{ search:'/api/acmeclient/actions/search',
|
||||
get:'/api/acmeclient/actions/get/',
|
||||
set:'/api/acmeclient/actions/set/',
|
||||
add:'/api/acmeclient/actions/add/',
|
||||
del:'/api/acmeclient/actions/del/',
|
||||
toggle:'/api/acmeclient/actions/toggle/',
|
||||
options: {
|
||||
rowCount:[10,25,50,100,500,1000]
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
|
||||
<li class="active"><a data-toggle="tab" href="#actions">{{ lang._('Restart Actions') }}</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content content-box tab-content">
|
||||
<div id="actions" class="tab-pane fade in active">
|
||||
<table id="grid-actions" class="table table-condensed table-hover table-striped table-responsive" data-editDialog="DialogAction">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="enabled" data-width="6em" data-type="string" data-formatter="rowtoggle">{{ lang._('Enabled') }}</th>
|
||||
<th data-column-id="name" data-type="string">{{ lang._('Name') }}</th>
|
||||
<th data-column-id="description" data-type="string">{{ lang._('Description') }}</th>
|
||||
<th data-column-id="commands" data-width="7em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
|
||||
<th data-column-id="uuid" data-type="string" data-identifier="true" data-visible="false">{{ lang._('ID') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<button data-action="add" type="button" class="btn btn-xs btn-default"><span class="fa fa-plus"></span></button>
|
||||
<button data-action="deleteSelected" type="button" class="btn btn-xs btn-default"><span class="fa fa-trash-o"></span></button>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# include dialogs #}
|
||||
{{ partial("layout_partials/base_dialog",['fields':formDialogAction,'id':'DialogAction','label':'Edit Restart Action'])}}
|
||||
|
|
@ -269,12 +269,14 @@ POSSIBILITY OF SUCH DAMAGE.
|
|||
{
|
||||
if (gridParams['sign'] != undefined) {
|
||||
var uuid=$(this).data("row-id");
|
||||
stdDialogRemoveItem('Sign/renew selected certificate?',function() {
|
||||
ajaxCall(url=gridParams['sign'] + uuid,
|
||||
sendData={},callback=function(data,status){
|
||||
stdDialogRemoveItem('Forcefully (re-)issue the selected certificate?',function() {
|
||||
// Handle HAProxy integration (no-op if not applicable)
|
||||
ajaxCall(url="/api/acmeclient/settings/fetchHAProxyIntegration", sendData={}, callback=function(data,status) {
|
||||
ajaxCall(url=gridParams['sign'] + uuid,sendData={},callback=function(data,status){
|
||||
// reload grid after sign
|
||||
$("#"+gridId).bootgrid("reload");
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
console.log("[grid] action sign missing")
|
||||
|
|
@ -311,9 +313,12 @@ POSSIBILITY OF SUCH DAMAGE.
|
|||
*/
|
||||
$("#signallcertsAct").click(function(){
|
||||
//$("#signallcertsAct_progress").addClass("fa fa-spinner fa-pulse");
|
||||
ajaxCall(url="/api/acmeclient/service/signallcerts", sendData={}, callback=function(data,status) {
|
||||
// when done, disable progress animation.
|
||||
//$("#signallcertsAct_progress").removeClass("fa fa-spinner fa-pulse");
|
||||
// Handle HAProxy integration (no-op if not applicable)
|
||||
ajaxCall(url="/api/acmeclient/settings/fetchHAProxyIntegration", sendData={}, callback=function(data,status) {
|
||||
ajaxCall(url="/api/acmeclient/service/signallcerts", sendData={}, callback=function(data,status) {
|
||||
// when done, disable progress animation.
|
||||
//$("#signallcertsAct_progress").removeClass("fa fa-spinner fa-pulse");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -88,7 +88,12 @@ POSSIBILITY OF SUCH DAMAGE.
|
|||
}
|
||||
});
|
||||
|
||||
ajaxCall(url="/api/acmeclient/settings/fetchRBCron", sendData={}, callback=function(data,status) {
|
||||
// Handle cron integration
|
||||
ajaxCall(url="/api/acmeclient/settings/fetchCronIntegration", sendData={}, callback=function(data,status) {
|
||||
});
|
||||
|
||||
// Handle HAProxy integration
|
||||
ajaxCall(url="/api/acmeclient/settings/fetchHAProxyIntegration", sendData={}, callback=function(data,status) {
|
||||
});
|
||||
|
||||
// when done, disable progress animation
|
||||
|
|
@ -120,12 +125,15 @@ POSSIBILITY OF SUCH DAMAGE.
|
|||
});
|
||||
}
|
||||
|
||||
ajaxCall(url="/api/acmeclient/settings/fetchRBCron", sendData={}, callback=function(data,status) {
|
||||
});
|
||||
|
||||
// when done, disable progress animation
|
||||
$('[id*="reconfigureAct_progress"]').each(function(){
|
||||
$(this).removeClass("fa fa-spinner fa-pulse");
|
||||
// Handle cron integration
|
||||
ajaxCall(url="/api/acmeclient/settings/fetchCronIntegration", sendData={}, callback=function(data,status) {
|
||||
// Handle HAProxy integration
|
||||
ajaxCall(url="/api/acmeclient/settings/fetchHAProxyIntegration", sendData={}, callback=function(data,status) {
|
||||
// when done, disable progress animation
|
||||
$('[id*="reconfigureAct_progress"]').each(function(){
|
||||
$(this).removeClass("fa fa-spinner fa-pulse");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,9 @@ require_once("certs.inc");
|
|||
require_once("legacy_bindings.inc");
|
||||
require_once("interfaces.inc");
|
||||
require_once("util.inc");
|
||||
require_once("system.inc"); // required for Web UI restart action
|
||||
// Some stuff requires the almighty MVC framework.
|
||||
use OPNsense\Core\Backend;
|
||||
use OPNsense\Core\Config;
|
||||
use OPNsense\Base;
|
||||
use OPNsense\AcmeClient\AcmeClient;
|
||||
|
|
@ -112,6 +114,9 @@ function cert_action_validator($opt_cert_id)
|
|||
|
||||
$modelObj = new OPNsense\AcmeClient\AcmeClient;
|
||||
|
||||
// Store certs here after successful issue/renewal. Required for restart actions.
|
||||
$restart_certs = Array();
|
||||
|
||||
// Search for cert ID in configuration
|
||||
$configObj = Config::getInstance()->object();
|
||||
if (isset($configObj->OPNsense->AcmeClient->certificates)) {
|
||||
|
|
@ -204,9 +209,12 @@ function cert_action_validator($opt_cert_id)
|
|||
// Start acme client to issue or renew certificate
|
||||
$val_result = run_acme_validation($certObj, $valObj, $acctObj);
|
||||
if (!$val_result) {
|
||||
log_error("AcmeClient: issued/renewed certificate: " . (string)$certObj->name);
|
||||
// Import certificate to Cert Manager
|
||||
if (!import_certificate($certObj, $modelObj)) {
|
||||
//echo "DEBUG: cert import done\n";
|
||||
// Prepare certificate for restart action
|
||||
$restart_certs[] = $certObj;
|
||||
} else {
|
||||
log_error("AcmeClient: unable to import certificate: " . (string)$certObj->name);
|
||||
if (isset($options["A"])) {
|
||||
|
|
@ -214,6 +222,8 @@ function cert_action_validator($opt_cert_id)
|
|||
}
|
||||
return(1);
|
||||
}
|
||||
} elseif ($val_result == '99') {
|
||||
// Renewal not required. Do nothing.
|
||||
} else {
|
||||
// validation failure
|
||||
log_error("AcmeClient: validation for certificate failed: " . (string)$certObj->name);
|
||||
|
|
@ -247,6 +257,17 @@ function cert_action_validator($opt_cert_id)
|
|||
log_error("AcmeClient: no LE certificates found in configuration");
|
||||
return(1);
|
||||
}
|
||||
|
||||
// Run restart actions if an operation was successful.
|
||||
if (!empty($restart_certs)) {
|
||||
// Execute restart actions.
|
||||
if (!run_restart_actions($restart_certs, $modelObj)) {
|
||||
# Success.
|
||||
} else {
|
||||
log_error("AcmeClient: failed to execute some restart actions");
|
||||
}
|
||||
}
|
||||
|
||||
return(0);
|
||||
}
|
||||
|
||||
|
|
@ -389,8 +410,6 @@ function run_acme_account_registration($acctObj, $certObj, $modelObj)
|
|||
// Run acme client with HTTP-01 or DNS-01 validation to issue/renew certificate
|
||||
function run_acme_validation($certObj, $valObj, $acctObj)
|
||||
{
|
||||
// TODO: add support for other HTTP-01 validation services/methods
|
||||
|
||||
global $options;
|
||||
|
||||
// Collect account information
|
||||
|
|
@ -417,7 +436,7 @@ function run_acme_validation($certObj, $valObj, $acctObj)
|
|||
// Preparation to run acme client
|
||||
$acme_args = eval_optional_acme_args();
|
||||
$proc_env = array(); // env variables for proc_open()
|
||||
$proc_env['PATH'] = '/sbin:/bin:/usr/sbin:/usr/bin:/usr/games:/usr/local/sbin:/usr/local/bin:/root/bin';
|
||||
$proc_env['PATH'] = '/sbin:/bin:/usr/sbin:/usr/bin:/usr/games:/usr/local/sbin:/usr/local/bin';
|
||||
$proc_desc = array( // descriptor array for proc_open()
|
||||
0 => array("pipe", "r"), // stdin
|
||||
1 => array("pipe", "w"), // stdout
|
||||
|
|
@ -426,7 +445,15 @@ function run_acme_validation($certObj, $valObj, $acctObj)
|
|||
$proc_pipes = array();
|
||||
|
||||
// Do we need to issue or renew the certificate?
|
||||
$acme_action = !empty((string)$certObj->lastUpdate) ? "renew" : "issue";
|
||||
if (!empty((string)$certObj->lastUpdate) and !isset($options["F"])) {
|
||||
$acme_action = "renew";
|
||||
} else {
|
||||
// Default: Issue a new certificate.
|
||||
// If "-F" is specified, forcefully re-issue the cert, no matter if it's required.
|
||||
// NOTE: This is useful if altNames were changed or when switching
|
||||
// from acme staging to acme production servers.
|
||||
$acme_action = "issue";
|
||||
}
|
||||
|
||||
// Calculate next renewal date
|
||||
$last_update = !empty((string)$certObj->lastUpdate) ? (string)$certObj->lastUpdate : 0;
|
||||
|
|
@ -437,12 +464,12 @@ function run_acme_validation($certObj, $valObj, $acctObj)
|
|||
$renew_interval = (string)$certObj->renewInterval;
|
||||
$next_update = $last_update_time->add(new \DateInterval('P'.$renew_interval.'D'));
|
||||
|
||||
// Check if it's time to renew, otherwise report success
|
||||
// Check if it's time to renew the cert.
|
||||
if (isset($options["F"]) or ($current_time >= $next_update)) {
|
||||
$renew_cert = true;
|
||||
} else {
|
||||
// Renewal not yet required, report success
|
||||
return(0);
|
||||
// Renewal not yet required, report special code
|
||||
return(99);
|
||||
}
|
||||
|
||||
// Try HTTP-01 or DNS-01 validation?
|
||||
|
|
@ -841,6 +868,148 @@ function import_certificate($certObj, $modelObj)
|
|||
return(0);
|
||||
}
|
||||
|
||||
function run_restart_actions($certlist, $modelObj)
|
||||
{
|
||||
global $config;
|
||||
$return = 0;
|
||||
|
||||
// NOTE: Do NOT run any restart action twice, collect duplicates first.
|
||||
$restart_actions = Array();
|
||||
|
||||
// Check if there's something to do.
|
||||
if (!empty($certlist) and is_array($certlist)) {
|
||||
// Extract cert object
|
||||
foreach ($certlist as $certObj) {
|
||||
// Make sure the object is functional.
|
||||
if (empty($certObj->id)) {
|
||||
log_error("AcmeClient: failed to query certificate for restart action");
|
||||
continue;
|
||||
}
|
||||
// Extract restart actions
|
||||
$_actions = explode(',', $certObj->restartActions);
|
||||
// Walk through all linked restart actions.
|
||||
$_actions = explode(',', $certObj->restartActions);
|
||||
foreach ($_actions as $_action ) {
|
||||
// Extract restart action
|
||||
$action = $modelObj->getByActionID($_action);
|
||||
// Make sure the object is functional.
|
||||
if ($action === null) {
|
||||
log_error("AcmeClient: failed to retrieve restart action from certificate");
|
||||
} else {
|
||||
// Ignore disabled restart actions (even if they are still
|
||||
// linked to a certificated).
|
||||
if ((string)$action->enabled === "0") {
|
||||
continue;
|
||||
}
|
||||
// Store by UUID, automatically eliminates duplicates.
|
||||
$restart_actions[$_action] = $action;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run the collected restart actions.
|
||||
if (!empty($restart_actions) and is_array($restart_actions)) {
|
||||
// Required to run pre-defined commands.
|
||||
$backend = new Backend();
|
||||
// Extract cert object
|
||||
foreach ($restart_actions as $action) {
|
||||
// Run pre-defined or custom command?
|
||||
log_error("AcmeClient: running restart action: " . $action->name);
|
||||
switch ((string)$action->type) {
|
||||
case 'restart_gui':
|
||||
$response = system_webgui_configure();
|
||||
break;
|
||||
case 'restart_haproxy':
|
||||
$response = $backend->configdRun("haproxy restart");
|
||||
break;
|
||||
case 'configd':
|
||||
// Make sure a configd command was specified.
|
||||
if (empty((string)$action->configd)) {
|
||||
log_error("AcmeClient: no configd command specified for restart action: " . $action->name);
|
||||
$result = '1';
|
||||
continue; // Continue with next action.
|
||||
}
|
||||
$response = $backend->configdRun((string)$action->configd);
|
||||
break;
|
||||
case 'custom':
|
||||
// Make sure a custom command was specified.
|
||||
if (empty((string)$action->custom)) {
|
||||
log_error("AcmeClient: no custom command specified for restart action: " . $action->name);
|
||||
$result = '1';
|
||||
continue; // Continue with next action.
|
||||
}
|
||||
|
||||
// Prepare to run the command.
|
||||
$proc_env = array(); // env variables for proc_open()
|
||||
$proc_env['PATH'] = '/sbin:/bin:/usr/sbin:/usr/bin:/usr/games:/usr/local/sbin:/usr/local/bin';
|
||||
$proc_desc = array( // descriptor array for proc_open()
|
||||
0 => array("pipe", "r"), // stdin
|
||||
1 => array("pipe", "w"), // stdout
|
||||
2 => array("pipe", "w") // stderr
|
||||
);
|
||||
$proc_pipes = array();
|
||||
$proc_stdout = '';
|
||||
$proc_stderr = '';
|
||||
$result = ''; // exit code (or '99' in case of timeout)
|
||||
|
||||
// TODO: Make the timeout configurable.
|
||||
$timeout = '600';
|
||||
$starttime = time();
|
||||
|
||||
$proc_cmd = (string)$action->custom;
|
||||
$proc = proc_open($proc_cmd, $proc_desc, $proc_pipes, null, $proc_env);
|
||||
|
||||
// Make sure the resource could be setup properly
|
||||
if (is_resource($proc)) {
|
||||
fclose($proc_pipes[0]);
|
||||
|
||||
// Wait until process terminates normally
|
||||
while(is_resource($proc))
|
||||
{
|
||||
$proc_stdout .= stream_get_contents($proc_pipes[1]);
|
||||
$proc_stderr .= stream_get_contents($proc_pipes[2]);
|
||||
|
||||
// Check if timeout is reached
|
||||
if(($timeout !== false) and ((time() - $starttime) > $timeout))
|
||||
{
|
||||
// Terminate process if timeout is reached
|
||||
log_error("AcmeClient: timeout running restart action: " . $action->name);
|
||||
proc_terminate($proc, 9);
|
||||
$result = '99';
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if process terminated normally
|
||||
$status = proc_get_status($proc);
|
||||
if(!$status['running'])
|
||||
{
|
||||
fclose($proc_pipes[1]);
|
||||
fclose($proc_pipes[2]);
|
||||
proc_close($proc);
|
||||
$result = $status['exitcode'];
|
||||
break;
|
||||
}
|
||||
|
||||
usleep(100000);
|
||||
}
|
||||
} else {
|
||||
log_error("AcmeClient: unable to initiate restart action: " . $action->name);
|
||||
continue; // Continue with next action.
|
||||
}
|
||||
$return = $result;
|
||||
break;
|
||||
default:
|
||||
log_error("AcmeClient: an invalid restart action was specified: " . (string)$action->type);
|
||||
$return = 1;
|
||||
continue; // Continue with next action.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return($return);
|
||||
}
|
||||
|
||||
// taken from certs.inc
|
||||
function local_cert_get_subject_array($str_crt, $decode = true)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ compress.cache-dir = "/tmp/acmelighttpdcompress/"
|
|||
compress.filetype = ("text/plain","text/css", "text/xml", "text/javascript" )
|
||||
|
||||
server.max-request-size = 4096
|
||||
server.tag = "lighttpd/ACME"
|
||||
|
||||
expire.url = ( "" => "access 10 hours" )
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue