mirror of
https://github.com/netbirdio/plugins.git
synced 2026-05-22 18:44:07 -07:00
AcmeClient: Added automation feature: cert upload via sftp
This commit is contained in:
+51
@@ -31,6 +31,7 @@ namespace OPNsense\AcmeClient\Api;
|
||||
|
||||
use \OPNsense\Base\ApiMutableModelControllerBase;
|
||||
use \OPNsense\Base\UIModelGrid;
|
||||
use \OPNsense\Core\Backend;
|
||||
use \OPNsense\Core\Config;
|
||||
use \OPNsense\AcmeClient\AcmeClient;
|
||||
|
||||
@@ -73,4 +74,54 @@ class ActionsController extends ApiMutableModelControllerBase
|
||||
{
|
||||
return $this->searchBase('actions.action', array('enabled', 'name', 'description'), 'name');
|
||||
}
|
||||
|
||||
public function sftpGetIdentityAction()
|
||||
{
|
||||
$result = ["status" => "unavailable"];
|
||||
|
||||
if ($response = $this->callBackend(["show-sftp-identity"], ["sftp_identity_type", "sftp_host"])) {
|
||||
$result["status"] = "ok";
|
||||
$result["identity"] = $response;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function sftpTestConnectionAction()
|
||||
{
|
||||
if ($response = $this->callBackend(
|
||||
["test-sftp-connection"],
|
||||
["sftp_host", "sftp_host_key", "sftp_port", "sftp_user", "sftp_identity_type", "sftp_remote_path"])) {
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
return ["status" => "unavailable"];
|
||||
}
|
||||
|
||||
private function callBackend(array $command, array $arguments = [])
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$backend = new Backend();
|
||||
|
||||
foreach ($arguments as $name) {
|
||||
$command[] = $this->request->getPost($name);
|
||||
}
|
||||
|
||||
$command = array_map(function ($value) {
|
||||
return escapeshellarg(empty($value = trim($value)) ? "__default_value" : $value);
|
||||
}, $command);
|
||||
|
||||
if ($result = trim($backend->configdRun("acmeclient " . join(" ", $command)))) {
|
||||
if (preg_match('/^\[.+\]$/ms', $result) || preg_match('/^\{.+\}$/ms', $result)) {
|
||||
try {
|
||||
$result = json_decode($result, true, 64, JSON_THROW_ON_ERROR);
|
||||
} catch (\Exception $ignored) {/*pass as is when json parsing fails*/}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+62
@@ -40,6 +40,68 @@
|
||||
<type>text</type>
|
||||
<help>Access token for Highwinds API.</help>
|
||||
</field>
|
||||
<field>
|
||||
<label>Required Parameters</label>
|
||||
<type>header</type>
|
||||
<style>method_table method_table_upload_sftp</style>
|
||||
</field>
|
||||
<field>
|
||||
<id>action.sftp_host</id>
|
||||
<label>SFTP Host</label>
|
||||
<type>text</type>
|
||||
<help>IP address or hostname of the SFTP server.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>action.sftp_port</id>
|
||||
<label>SFTP Port</label>
|
||||
<type>text</type>
|
||||
<help>SFTP server port. Leave blank to use default "22".</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>action.sftp_host_key</id>
|
||||
<label>Host Key</label>
|
||||
<type>text</type>
|
||||
<help>SFTP server host key, formatted as in 'known_hosts'.
|
||||
Leave blank to auto accept host key on first connect (not as secure as specifying it).</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>action.sftp_user</id>
|
||||
<label>Username</label>
|
||||
<type>text</type>
|
||||
<help>The username to login to the SFTP server.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>action.sftp_identity_type</id>
|
||||
<label>Identity Type</label>
|
||||
<type>dropdown</type>
|
||||
<help>The type of identify to present to the SFTP server for authorization. Select 'none' to use default "ECDSA".</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>action.sftp_remote_path</id>
|
||||
<label>Remote Path</label>
|
||||
<type>text</type>
|
||||
<help>Path on the SFTP server to change to after login.
|
||||
The path can be absolute or relative to home and must exist.
|
||||
Leave blank to not change path after login.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>action.sftp_chmod</id>
|
||||
<label>Permission (Public Keys)</label>
|
||||
<type>text</type>
|
||||
<help>Unix permission to apply to uploaded public keys. Leave blank to use default "0440".</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>action.sftp_chmod_key</id>
|
||||
<label>Permission (Private Keys)</label>
|
||||
<type>text</type>
|
||||
<help>Unix permission to apply to uploaded private keys. Leave blank to use default "0400".</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>action.sftp_chgrp</id>
|
||||
<label>Group</label>
|
||||
<type>text</type>
|
||||
<help>Unix group id to apply to all uploaded files. Leave blank to not change the group.</help>
|
||||
</field>
|
||||
<field>
|
||||
<label>Required Parameters</label>
|
||||
<type>header</type>
|
||||
|
||||
@@ -738,6 +738,7 @@
|
||||
<restart_haproxy>Restart HAProxy (OPNsense plugin)</restart_haproxy>
|
||||
<restart_nginx>Restart Nginx (OPNsense plugin)</restart_nginx>
|
||||
<upload_highwinds>Upload certificate to Highwinds CDN</upload_highwinds>
|
||||
<upload_sftp>Upload certificate via SFTP</upload_sftp>
|
||||
<configd>System or Plugin Command (select below)</configd>
|
||||
</OptionValues>
|
||||
</type>
|
||||
@@ -751,6 +752,58 @@
|
||||
<mask>/^.{1,1024}$/u</mask>
|
||||
<ValidationMessage>Should be a string between 1 and 1024 characters.</ValidationMessage>
|
||||
</highwinds_access_token>
|
||||
<sftp_host type="TextField">
|
||||
<Required>N</Required>
|
||||
<mask>/^.{1,255}$/u</mask>
|
||||
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
|
||||
</sftp_host>
|
||||
<sftp_host_key type="TextField">
|
||||
<Required>N</Required>
|
||||
<!-- Key format: (comment)? key-type :SPACE: key-base64 (:SPACE: comment)?
|
||||
Reference: https://stackoverflow.com/a/475217 -->
|
||||
<mask>/^.+?\s(?:[a-z0-9+\/]{4})*(?:[a-z0-9+\/]{2}==|[a-z0-9+\/]{3}=)?(?:\s.+?)?$/i</mask>
|
||||
<ValidationMessage>Should be a valid public SSH host key (see "known_hosts").</ValidationMessage>
|
||||
</sftp_host_key>
|
||||
<sftp_port type="IntegerField">
|
||||
<Required>N</Required>
|
||||
<MinimumValue>1</MinimumValue>
|
||||
<MaximumValue>49151</MaximumValue>
|
||||
<default>22</default>
|
||||
<ValidationMessage>Should be a valid port number between 1 and 49151.</ValidationMessage>
|
||||
</sftp_port>
|
||||
<sftp_user type="TextField">
|
||||
<Required>N</Required>
|
||||
<mask>/^.{1,128}$/u</mask>
|
||||
<ValidationMessage>Should be a string between 1 and 128 characters.</ValidationMessage>
|
||||
</sftp_user>
|
||||
<sftp_identity_type type="OptionField">
|
||||
<Required>N</Required>
|
||||
<OptionValues>
|
||||
<ecdsa>ECDSA</ecdsa>
|
||||
<rsa>RSA</rsa>
|
||||
<ed25519>ed25519</ed25519>
|
||||
</OptionValues>
|
||||
</sftp_identity_type>
|
||||
<sftp_remote_path type="TextField">
|
||||
<Required>N</Required>
|
||||
<mask>/^.{1,512}$/u</mask>
|
||||
<ValidationMessage>Should be a string between 1 and 512 characters.</ValidationMessage>
|
||||
</sftp_remote_path>
|
||||
<sftp_chgrp type="TextField">
|
||||
<Required>N</Required>
|
||||
<mask>/^[0-9]+$/u</mask>
|
||||
<ValidationMessage>Should be a numeric value.</ValidationMessage>
|
||||
</sftp_chgrp>
|
||||
<sftp_chmod type="TextField">
|
||||
<Required>N</Required>
|
||||
<mask>/^0[0-9]{3}$/u</mask>
|
||||
<ValidationMessage>A unix permission, 4 digits (e.g. 0440).</ValidationMessage>
|
||||
</sftp_chmod>
|
||||
<sftp_chmod_key type="TextField">
|
||||
<Required>N</Required>
|
||||
<mask>/^0[0-9]{3}$/u</mask>
|
||||
<ValidationMessage>A unix permission, 4 digits (e.g. 0400).</ValidationMessage>
|
||||
</sftp_chmod_key>
|
||||
<configd type="ConfigdActionsField">
|
||||
<filters>
|
||||
<description>/^(?!.*(Let\'s\ Encrypt|acme|[fF]irmware))([\S\s]{1,255})/</description>
|
||||
|
||||
@@ -60,7 +60,135 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
$(".method_table_"+$(this).val()).show();
|
||||
});
|
||||
$("#action\\.type").change();
|
||||
})
|
||||
});
|
||||
|
||||
// Helpers for extra buttons and status divs
|
||||
function makeButton(label, buttonGroup, buttonClass) {
|
||||
var button = $('<button class="btn" type="button">'
|
||||
+ '<span class="btn-text"></span>'
|
||||
+ '<i class="fa fa-spinner fa-pulse" style="margin-left: 0.5em;"></i></button>');
|
||||
button.addClass(buttonClass || "btn-primary");
|
||||
$('.fa-spinner', button).hide();
|
||||
$('.btn-text', button).html(label);
|
||||
|
||||
var targetContainer = $("#DialogAction .modal-footer"),
|
||||
targetId = "method_table_" + buttonGroup,
|
||||
target = $("." + targetId, targetContainer);
|
||||
|
||||
if (!target.is('span')) {
|
||||
target = $('<span class="method_table" style="float: left"></span>')
|
||||
.addClass(targetId)
|
||||
.prependTo(targetContainer)
|
||||
.hide();
|
||||
}
|
||||
|
||||
return button.appendTo(target);
|
||||
}
|
||||
|
||||
function makeStatusDiv(anchor, statusClass) {
|
||||
return $('<div class="alert method_table" role="alert" style="word-break: break-all"></div>')
|
||||
.appendTo($(anchor).closest("table").find("thead th[colspan=3]").first())
|
||||
.addClass(statusClass || 'alert-info')
|
||||
.hide();
|
||||
}
|
||||
|
||||
// SFTP - Identity show button
|
||||
(function ($identityType) {
|
||||
var identityDiv = makeStatusDiv($identityType);
|
||||
|
||||
makeButton("{{ lang._('Show Identity') }}", "upload_sftp", "btn-info")
|
||||
.click(function () {
|
||||
identityDiv.hide();
|
||||
var button = $(this);
|
||||
button.prop('disabled', true).find(".fa-spinner").show();
|
||||
|
||||
ajaxCall("/api/acmeclient/actions/sftpGetIdentity", getFormData("DialogAction").action, function (data, status) {
|
||||
button.prop('disabled', false).find(".fa-spinner").hide();
|
||||
|
||||
if (status === "success" && data.status === "ok")
|
||||
identityDiv.text(data.identity).show();
|
||||
else
|
||||
identityDiv.text("{{ lang._('Failed loading identity') }}").show();
|
||||
});
|
||||
});
|
||||
|
||||
// Hide when input changes that influences the identity.
|
||||
$identityType.change(function() {
|
||||
identityDiv.hide();
|
||||
});
|
||||
})($('#action\\.sftp_identity_type'));
|
||||
|
||||
// SFTP - Connection test button
|
||||
(function ($user) {
|
||||
var statusDiv = makeStatusDiv($user, 'alert-success').html(
|
||||
'<div class="message"></div>'
|
||||
+ '<div class="detail-enabler" style="cursor: pointer"><i class="fa fa-plus-square"></i></div>'
|
||||
+ '<div class="detail" style="font-family: monospace"></div>');
|
||||
|
||||
statusDiv.find(".detail-enabler").click(function() {
|
||||
$(".detail", statusDiv).show();
|
||||
$(this).hide();
|
||||
});
|
||||
|
||||
var errors = [
|
||||
{cond: ["connect_failed", "invalid_parameters"], msg: "{{ lang._('Host or username not specified.') }}"},
|
||||
{cond: ["connect_failed", "host_not_trusted"], msg: "{{ lang._('Host cannot be trusted.') }}"},
|
||||
{cond: ["connect_failed", "host_not_resolved"], msg: "{{ lang._('Failed to resolve hostname.') }}"},
|
||||
{cond: ["connect_failed", "connection_refused"], msg: "{{ lang._('Connection to host refused.') }}"},
|
||||
{cond: ["connect_failed", "network_timeout"], msg: "{{ lang._('Connection timed out.') }}"},
|
||||
{cond: ["connect_failed", "network_unreachable"], msg: "{{ lang._('Host not reachable.') }}"},
|
||||
{cond: ["connect_failed", "permission_denied"], msg: "{{ lang._('Host does not permit a connection for the specified user & identity.') }}"},
|
||||
{cond: ["connect_failed"], msg: "{{ lang._('Failed to connect to host.') }}"},
|
||||
{cond: ["change_home_dir_failed"], msg: "{{ lang._('Failed to change the remote path.') }}"},
|
||||
{cond: ["permission_denied"], msg: "{{ lang._('Uploads are not allowed to the specified remote path.') }}"},
|
||||
{msg: "{{ lang._('Test failed, see details.') }}"},
|
||||
];
|
||||
|
||||
makeButton("{{ lang._('Test Connection') }}", "upload_sftp")
|
||||
.click(function () {
|
||||
statusDiv.hide();
|
||||
var button = $(this);
|
||||
button.prop('disabled', true).find(".fa-spinner").show();
|
||||
|
||||
ajaxCall("/api/acmeclient/actions/sftpTestConnection", getFormData("DialogAction").action, function (data, status) {
|
||||
button.prop('disabled', false).find(".fa-spinner").hide();
|
||||
|
||||
var message = "", detail = "", statusClass = "alert-warning";
|
||||
|
||||
if (status === "success") {
|
||||
if (data.success === true) {
|
||||
statusClass = "alert-success";
|
||||
message = "{{ lang._('Connection and upload test succeeded.') }}"
|
||||
} else {
|
||||
detail = JSON.stringify(data, null, ' ');
|
||||
|
||||
for (var i = 0; i < errors.length; i++) {
|
||||
var error = errors[i],
|
||||
matching = (error.cond || []).filter(function (condition) {
|
||||
return data[condition] === true;
|
||||
});
|
||||
|
||||
if (matching.length === error.cond.length) {
|
||||
message = error.msg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
message = "{{ lang._('Test not possible. Failed to talk to firewall backend.') }}";
|
||||
}
|
||||
|
||||
$(".message", statusDiv).html(message);
|
||||
$(".detail", statusDiv).text(detail).hide();
|
||||
$(".detail-enabler", statusDiv).toggle(detail !== "");
|
||||
|
||||
statusDiv.removeClass("alert-success alert-warning").addClass(statusClass).show();
|
||||
});
|
||||
});
|
||||
})($('#action\\.sftp_user'));
|
||||
|
||||
// Eagerly hiding method tables to avoid contents popping up when opening the dialog for the first time.
|
||||
$(".method_table").hide();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
@@ -1290,6 +1290,9 @@ function run_restart_actions($certlist, $modelObj)
|
||||
case 'upload_highwinds':
|
||||
$response = $backend->configdRun("acmeclient upload_highwinds ${cert_id} ${action_id}");
|
||||
break;
|
||||
case 'upload_sftp':
|
||||
$response = $backend->configdRun("acmeclient upload-sftp ${cert_id} ${action_id}");
|
||||
break;
|
||||
case 'configd':
|
||||
// Make sure a configd command was specified.
|
||||
if (empty((string)$action->configd)) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -84,6 +84,24 @@ parameters:-c %s -a %s
|
||||
type:script
|
||||
message:uploading a certificate to highwinds
|
||||
|
||||
[upload-sftp]
|
||||
command:/usr/local/opnsense/scripts/OPNsense/AcmeClient/upload_sftp.php
|
||||
parameters:--certificates=%s --automation-id=%s
|
||||
type:script
|
||||
message:uploading a certificate to sftp server
|
||||
|
||||
[test-sftp-connection]
|
||||
command:/usr/local/opnsense/scripts/OPNsense/AcmeClient/upload_sftp.php
|
||||
parameters:--host=%s --host-key=%s --port=%s --user=%s --identity-type=%s --remote-path=%s --no-error test-connection
|
||||
type:script_output
|
||||
message:testing connection to sftp server
|
||||
|
||||
[show-sftp-identity]
|
||||
command:/usr/local/opnsense/scripts/OPNsense/AcmeClient/upload_sftp.php
|
||||
parameters:--identity-type=%s --host=%s show-identity
|
||||
type:script_output
|
||||
message:prints the public key used to connect to sftp server
|
||||
|
||||
[reset-acme-client]
|
||||
command:/usr/bin/find /var/etc/acme-client/home /var/etc/acme-client/configs /var/etc/acme-client/certs /var/etc/acme-client/keys /var/etc/acme-client/accounts -type f -delete
|
||||
parameters:
|
||||
|
||||
Reference in New Issue
Block a user