From 952af261e96bf1d645dc19732de00da51d4d5ba2 Mon Sep 17 00:00:00 2001 From: Frank Wall Date: Thu, 9 Feb 2023 00:16:09 +0100 Subject: [PATCH] sysutils/auto-recovery: a new plugin, closes #2976 --- sysutils/auto-recovery/Makefile | 6 + sysutils/auto-recovery/pkg-descr | 9 + .../AutoRecovery/Api/ServiceController.php | 92 +++++++ .../AutoRecovery/Api/SettingsController.php | 46 ++++ .../OPNsense/AutoRecovery/IndexController.php | 47 ++++ .../OPNsense/AutoRecovery/forms/general.xml | 29 +++ .../models/OPNsense/AutoRecovery/ACL/ACL.xml | 9 + .../OPNsense/AutoRecovery/AutoRecovery.php | 38 +++ .../OPNsense/AutoRecovery/AutoRecovery.xml | 37 +++ .../OPNsense/AutoRecovery/Menu/Menu.xml | 5 + .../views/OPNsense/AutoRecovery/index.volt | 113 +++++++++ .../OPNsense/AutoRecovery/auto-recovery.sh | 231 ++++++++++++++++++ .../conf/actions.d/actions_autorecovery.conf | 17 ++ 13 files changed, 679 insertions(+) create mode 100644 sysutils/auto-recovery/Makefile create mode 100644 sysutils/auto-recovery/pkg-descr create mode 100644 sysutils/auto-recovery/src/opnsense/mvc/app/controllers/OPNsense/AutoRecovery/Api/ServiceController.php create mode 100644 sysutils/auto-recovery/src/opnsense/mvc/app/controllers/OPNsense/AutoRecovery/Api/SettingsController.php create mode 100644 sysutils/auto-recovery/src/opnsense/mvc/app/controllers/OPNsense/AutoRecovery/IndexController.php create mode 100644 sysutils/auto-recovery/src/opnsense/mvc/app/controllers/OPNsense/AutoRecovery/forms/general.xml create mode 100644 sysutils/auto-recovery/src/opnsense/mvc/app/models/OPNsense/AutoRecovery/ACL/ACL.xml create mode 100644 sysutils/auto-recovery/src/opnsense/mvc/app/models/OPNsense/AutoRecovery/AutoRecovery.php create mode 100644 sysutils/auto-recovery/src/opnsense/mvc/app/models/OPNsense/AutoRecovery/AutoRecovery.xml create mode 100644 sysutils/auto-recovery/src/opnsense/mvc/app/models/OPNsense/AutoRecovery/Menu/Menu.xml create mode 100644 sysutils/auto-recovery/src/opnsense/mvc/app/views/OPNsense/AutoRecovery/index.volt create mode 100755 sysutils/auto-recovery/src/opnsense/scripts/OPNsense/AutoRecovery/auto-recovery.sh create mode 100644 sysutils/auto-recovery/src/opnsense/service/conf/actions.d/actions_autorecovery.conf diff --git a/sysutils/auto-recovery/Makefile b/sysutils/auto-recovery/Makefile new file mode 100644 index 00000000000..bde059c66f9 --- /dev/null +++ b/sysutils/auto-recovery/Makefile @@ -0,0 +1,6 @@ +PLUGIN_NAME= auto-recovery +PLUGIN_VERSION= 1.0 +PLUGIN_COMMENT= A configuration rollback functionality for OPNsense +PLUGIN_MAINTAINER= opnsense@moov.de + +.include "../../Mk/plugins.mk" diff --git a/sysutils/auto-recovery/pkg-descr b/sysutils/auto-recovery/pkg-descr new file mode 100644 index 00000000000..442f80f69ad --- /dev/null +++ b/sysutils/auto-recovery/pkg-descr @@ -0,0 +1,9 @@ +Set a timer to perform a rollback to a previous configuration state. +It is a safety net when making critical configuration on a remote device. + +Plugin Changelog +================ + +1.0 + +* initial release diff --git a/sysutils/auto-recovery/src/opnsense/mvc/app/controllers/OPNsense/AutoRecovery/Api/ServiceController.php b/sysutils/auto-recovery/src/opnsense/mvc/app/controllers/OPNsense/AutoRecovery/Api/ServiceController.php new file mode 100644 index 00000000000..2209967ba43 --- /dev/null +++ b/sysutils/auto-recovery/src/opnsense/mvc/app/controllers/OPNsense/AutoRecovery/Api/ServiceController.php @@ -0,0 +1,92 @@ +general->action; + if (!empty((string)$model->general->configd_command)) { + // Replace spaces with colons before passing the value to configdRun(). + $configdcmd = str_replace(' ', ':',(string)$model->general->configd_command); + } else { + $configdcmd = 'not_found'; + } + // Convert minutes to seconds + $countdown = 60 * (string)$model->general->countdown; + + $backend = new Backend(); + $response = $backend->configdRun("autorecovery countdown ${action} ${configdcmd} ${countdown}"); + return array("response" => $response); + } + + /** + * get remaining time for current countdown + * @return string + */ + public function timeAction() + { + $backend = new Backend(); + $response = $backend->configdRun("autorecovery status"); + return array("response" => $response); + } + + /** + * abort countdown + * @return string + */ + public function abortAction() + { + $backend = new Backend(); + $response = $backend->configdRun("autorecovery abort"); + return array("response" => $response); + } +} diff --git a/sysutils/auto-recovery/src/opnsense/mvc/app/controllers/OPNsense/AutoRecovery/Api/SettingsController.php b/sysutils/auto-recovery/src/opnsense/mvc/app/controllers/OPNsense/AutoRecovery/Api/SettingsController.php new file mode 100644 index 00000000000..63c58436496 --- /dev/null +++ b/sysutils/auto-recovery/src/opnsense/mvc/app/controllers/OPNsense/AutoRecovery/Api/SettingsController.php @@ -0,0 +1,46 @@ +view->pick('OPNsense/AutoRecovery/index'); + // fetch form data "general" in + $this->view->generalForm = $this->getForm("general"); + } +} diff --git a/sysutils/auto-recovery/src/opnsense/mvc/app/controllers/OPNsense/AutoRecovery/forms/general.xml b/sysutils/auto-recovery/src/opnsense/mvc/app/controllers/OPNsense/AutoRecovery/forms/general.xml new file mode 100644 index 00000000000..8e5063e46a2 --- /dev/null +++ b/sysutils/auto-recovery/src/opnsense/mvc/app/controllers/OPNsense/AutoRecovery/forms/general.xml @@ -0,0 +1,29 @@ +
+ + + header + + + autorecovery.general.countdown + + text + The number of minutes to wait before starting recovery procedures. + + + autorecovery.general.action + + dropdown + The recovery action that should be performed. + + + + header + + + + autorecovery.general.configd_command + + dropdown + Select a pre-defined system command which should be run. + +
diff --git a/sysutils/auto-recovery/src/opnsense/mvc/app/models/OPNsense/AutoRecovery/ACL/ACL.xml b/sysutils/auto-recovery/src/opnsense/mvc/app/models/OPNsense/AutoRecovery/ACL/ACL.xml new file mode 100644 index 00000000000..158808cfba9 --- /dev/null +++ b/sysutils/auto-recovery/src/opnsense/mvc/app/models/OPNsense/AutoRecovery/ACL/ACL.xml @@ -0,0 +1,9 @@ + + + System: Auto Recovery + + ui/autorecovery/* + api/autorecovery/* + + + diff --git a/sysutils/auto-recovery/src/opnsense/mvc/app/models/OPNsense/AutoRecovery/AutoRecovery.php b/sysutils/auto-recovery/src/opnsense/mvc/app/models/OPNsense/AutoRecovery/AutoRecovery.php new file mode 100644 index 00000000000..c8bf6ce5d72 --- /dev/null +++ b/sysutils/auto-recovery/src/opnsense/mvc/app/models/OPNsense/AutoRecovery/AutoRecovery.php @@ -0,0 +1,38 @@ + + //OPNsense/AutoRecovery + 1.0.0 + A configuration rollback functionality for OPNsense + + + + 10 + 1 + + 10080 + Please specify a value between 1 and 10080 minutes. + Y + + + Y + restore_reboot + + Restore config and reboot [default] + Restore config and restart all services + Restore config and run system command + Restore config + Reboot OPNsense + Run system command + Do nothing (for testing purpose) + + + + + /(.){1,255}/ + + Select a command from the list. + N + + + + diff --git a/sysutils/auto-recovery/src/opnsense/mvc/app/models/OPNsense/AutoRecovery/Menu/Menu.xml b/sysutils/auto-recovery/src/opnsense/mvc/app/models/OPNsense/AutoRecovery/Menu/Menu.xml new file mode 100644 index 00000000000..f89a638b565 --- /dev/null +++ b/sysutils/auto-recovery/src/opnsense/mvc/app/models/OPNsense/AutoRecovery/Menu/Menu.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/sysutils/auto-recovery/src/opnsense/mvc/app/views/OPNsense/AutoRecovery/index.volt b/sysutils/auto-recovery/src/opnsense/mvc/app/views/OPNsense/AutoRecovery/index.volt new file mode 100644 index 00000000000..afe8c2385ce --- /dev/null +++ b/sysutils/auto-recovery/src/opnsense/mvc/app/views/OPNsense/AutoRecovery/index.volt @@ -0,0 +1,113 @@ +{# + +OPNsense® is Copyright © 2023 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. + +#} + + + +
+ {{ partial("layout_partials/base_form",['fields':generalForm,'id':'frm_GeneralSettings'])}} +
+ +
+
+ +

{{ lang._('%sHOW IT WORKS:%s') | format('', '') }}

+ +
+ {{ lang._('%sPLEASE NOTE:%s Auto Recovery is a community plugin without support or guarantees. Auto Recovery can only restore the OPNsense system configuration to a previous state. It does not restore any other files nor does it revert any filesystem modifications. It certainly is not meant to replace a backup, nor does it protect against failed software upgrades.') | format('', '') }} +
+ + +
diff --git a/sysutils/auto-recovery/src/opnsense/scripts/OPNsense/AutoRecovery/auto-recovery.sh b/sysutils/auto-recovery/src/opnsense/scripts/OPNsense/AutoRecovery/auto-recovery.sh new file mode 100755 index 00000000000..03c1fc8f027 --- /dev/null +++ b/sysutils/auto-recovery/src/opnsense/scripts/OPNsense/AutoRecovery/auto-recovery.sh @@ -0,0 +1,231 @@ +#!/bin/sh + +# Configuration variables. +BASE_DIR='/var/etc/auto-recovery' +ABORT_FILE="${BASE_DIR}/autorecover.abort" +CONFIG_FILE='/conf/config.xml' +CONFIG_BACKUP_FILE="${BASE_DIR}/config.xml_recover" +CONFIG_ORIGINAL_FILE="${BASE_DIR}/config.xml_orig" +COUNTDOWN=0 +SCRIPT_NAME='AutoRecovery' +START_TIME="`date +%s`" +STATE_FILE="${BASE_DIR}/countdown.state" +VERBOSE=0 + +# Possible recovery actions. +DO_RESTORE=0 +DO_REBOOT=0 +DO_RELOAD=0 +DO_CONFIGD=0 + +# Recovery commands. +RESTORE_CMD='configctl template reload \*' +REBOOT_CMD='configctl system reboot' +RELOAD_CMD='configctl service reload all' +CONFIGD_CMD='configctl' + +# Print usage information. +usage() { + echo "usage: `basename $0` COUNTDOWN_IN_SECONDS" +} + +# Print fatal errors on STDERR and exit. +fatal() { + 1>&2 echo "[ERROR] $@" + syslog $@ + cleanup + exit 1 +} + +# Print errors on STDERR. +error() { + 1>&2 echo "[ERROR] $@" + syslog $@ +} + +# Print message on STDOUT. +log() { + 1>&2 echo "$@" + syslog $@ +} + +# Print debug messages. +verbose() { + [ "$VERBOSE" = "1" ] && echo $@ +} + +# Send message to syslog. +syslog() { + logger "${SCRIPT_NAME}: $@" +} + +# Remove state information. +cleanup() { + rm -f $ABORT_FILE $STATE_FILE +} + +# Check args. +if [ "$#" -lt "1" ]; then + usage + exit 1 +fi + +# Get user input. +while [ "$1" != "" ]; do + case "$1" in + --action*) + RECOVERY_ACTION="`echo $1 | cut -d= -f2`" + shift + ;; + --configd*) + _tmp_cmd="${CONFIGD_CMD} `echo $1 | cut -d= -f2`" + # Replace colons with spaces to restore correct configd command. + CONFIGD_CMD="`echo ${_tmp_cmd} | sed -e 's/:/ /'`" + shift + ;; + -v*) + VERBOSE=1 + shift + ;; + *) + COUNTDOWN=$1 + shift + ;; + esac +done + +# Enable selected recovery actions. +case "$RECOVERY_ACTION" in + restore_reboot) + DO_RESTORE=1 + DO_REBOOT=1 + ;; + restore_reload) + DO_RESTORE=1 + DO_RELOAD=1 + ;; + restore_configd) + DO_RESTORE=1 + DO_CONFIGD=1 + ;; + restore) + DO_RESTORE=1 + ;; + reboot) + DO_REBOOT=1 + ;; + configd) + DO_CONFIGD=1 + ;; + noop) + log "test mode enabled" + ;; + *) + usage + exit 1 + ;; +esac + +# Verify args +if [ "${COUNTDOWN}" -le 1 ]; then + usage + exit 1 +fi + +mkdir -p $BASE_DIR || fatal "Unable to create ${BASE_DIR}" + +# Save the current configuration. +if [ "${DO_RESTORE}" == "1" ]; then + cp $CONFIG_FILE $CONFIG_BACKUP_FILE + if [ "$?" -gt "0" ]; then + fatal "Unable to create a backup of the config file" + fi +fi + +# Calculate target time. +TARGET_TIME="`expr $START_TIME + $COUNTDOWN`" + +# Start countdown. +echo $TARGET_TIME > $STATE_FILE +if [ "$?" -gt "0" ]; then + fatal "Unable to create state file" +fi +log "Countdown initiated: ${COUNTDOWN} seconds remaining" + +# Main loop +while [ 1 ]; do + # Check if the countdown must be aborted. + if [ -f "$ABORT_FILE" ]; then + verbose "Found file: $ABORT_FILE" + log "Countdown aborted on user request" + cleanup + exit 0 + fi + + # Check if target time is reached. + cur_time="`date +%s`" + if [ "$cur_time" -ge "$TARGET_TIME" ]; then + log "Performing system recovery... (${RECOVERY_ACTION})" + + # Restore configuration backup. + if [ "${DO_RESTORE}" == "1" ]; then + log "Restoring configuration backup" + + # Perform an additional backup of the currently running configuration. + # Just for extra safekeeping if the recovery process goes horribly wrong. + cp $CONFIG_FILE $CONFIG_ORIGINAL_FILE + + # Restore the backup configuration. + cp $CONFIG_BACKUP_FILE $CONFIG_FILE + if [ "$?" -gt "0" ]; then + fatal "Unable to restore config file" + fi + + # Reload all templates. + eval $RESTORE_CMD + if [ "$?" -gt "0" ]; then + error "Reload templates command exited with non-zero exit code" + fi + fi + + # Restart all services. + if [ "${DO_RELOAD}" == "1" ]; then + log "Restarting all services" + eval $RELOAD_CMD + if [ "$?" -gt "0" ]; then + error "Restart services command exited with non-zero exit code" + fi + fi + + # Run configd command. + if [ "${DO_CONFIGD}" == "1" ]; then + log "Running system command: ${CONFIGD_CMD}" + eval $CONFIGD_CMD + if [ "$?" -gt "0" ]; then + error "System command exited with non-zero exit code" + fi + fi + + # Reboot the system. + if [ "${DO_REBOOT}" == "1" ]; then + # Need to cleanup before issuing the reboot command, otherwise + # the state file would not get removed. + cleanup + log "Rebooting the system" + eval $REBOOT_CMD + if [ "$?" -gt "0" ]; then + error "Reboot command exited with non-zero exit code" + fi + fi + + cleanup + exit 0 + fi + + # Print status information. + remaining_time="`expr $TARGET_TIME - $cur_time`" + verbose "Countdown: $remaining_time seconds" + sleep 1 +done + +exit 0 diff --git a/sysutils/auto-recovery/src/opnsense/service/conf/actions.d/actions_autorecovery.conf b/sysutils/auto-recovery/src/opnsense/service/conf/actions.d/actions_autorecovery.conf new file mode 100644 index 00000000000..d90d1e7d112 --- /dev/null +++ b/sysutils/auto-recovery/src/opnsense/service/conf/actions.d/actions_autorecovery.conf @@ -0,0 +1,17 @@ +[countdown] +command:/usr/sbin/daemon -f /usr/local/opnsense/scripts/OPNsense/AutoRecovery/auto-recovery.sh +parameters:--action=%s --configd=%s %s +type:script +message:starting auto recovery countdown + +[abort] +command:touch /var/etc/auto-recovery/autorecover.abort +parameters: +type:script_output +message:aborting auto recovery countdown + +[status] +command:cat /var/etc/auto-recovery/countdown.state 2>/dev/null || echo -1 +parameters: +type:script_output +message:requesting auto recovery status