├── +POST_INSTALL ├── .githooks └── pre-commit ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── pkg-descr ├── src ├── etc │ ├── inc │ │ └── plugins.inc.d │ │ │ └── autossh.inc │ └── rc.d │ │ └── opnsense-autossh └── opnsense │ ├── mvc │ └── app │ │ ├── controllers │ │ └── ThreatPatrols │ │ │ └── Autossh │ │ │ ├── Api │ │ │ ├── AutosshApiControllerBase.php │ │ │ ├── ConnectionsController.php │ │ │ ├── KeysController.php │ │ │ └── TunnelsController.php │ │ │ ├── ConnectionsController.php │ │ │ ├── SettingsController.php │ │ │ └── forms │ │ │ ├── keys.xml │ │ │ └── tunnels.xml │ │ ├── models │ │ └── ThreatPatrols │ │ │ └── Autossh │ │ │ ├── ACL │ │ │ └── ACL.xml │ │ │ ├── Autossh.php │ │ │ ├── Autossh.xml │ │ │ ├── Menu │ │ │ └── Menu.xml │ │ │ └── Migrations │ │ │ └── M0_3_0.php │ │ └── views │ │ └── ThreatPatrols │ │ └── Autossh │ │ ├── connections.volt │ │ └── settings.volt │ ├── scripts │ └── ThreatPatrols │ │ └── Autossh │ │ ├── .pylintrc │ │ ├── autossh.py │ │ ├── autossh │ │ ├── __init__.py │ │ ├── __version__.py │ │ ├── exceptions.py │ │ ├── utils │ │ │ ├── __init__.py │ │ │ ├── config_helper.py │ │ │ ├── connection_status.py │ │ │ ├── content_helpers.py │ │ │ ├── exec_helpers.py │ │ │ ├── file_helpers.py │ │ │ ├── host_keys.py │ │ │ ├── key_gen.py │ │ │ ├── logger_helpers.py │ │ │ ├── random_helpers.py │ │ │ └── system_helpers.py │ │ └── vars.py │ │ └── tests │ │ ├── __init__.py │ │ ├── test_content_helpers.py │ │ ├── test_exec_helpers.py │ │ ├── test_random_helpers.py │ │ └── test_system_helpers.py │ └── service │ ├── conf │ └── actions.d │ │ └── actions_autossh.conf │ └── templates │ └── ThreatPatrols │ └── Autossh │ ├── +TARGETS │ ├── autossh │ ├── autossh.conf │ └── syslog-ng-autossh.conf └── tools └── devhost-installer.sh /+POST_INSTALL: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Rebuild system-menus and plugin-templates 4 | /usr/local/etc/rc.configure_plugins 5 | 6 | # Restart configd with latest templates 7 | service configd restart 8 | -------------------------------------------------------------------------------- /.githooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | if [ $(ls -1 | grep -c '^src$') -eq 1 ]; then 6 | 7 | # Python black code test 8 | if [ $(which black | wc -l) -gt 0 ]; then 9 | if [ $(black --diff --exclude vendor --line-length=120 src/opnsense/scripts/ThreatPatrols/Autossh | wc -l) -gt 0 ]; then 10 | echo 11 | echo " >> FATAL: Stopping commit-action until Python black-returncode is zero" 12 | echo 13 | exit 1 14 | fi 15 | fi 16 | 17 | # Unexpected vars in code test 18 | if [ $(grep cyberco -s -r src/opnsense/scripts/ThreatPatrols/Autossh | wc -l) -gt 0 ]; then 19 | echo 20 | echo " >> FATAL: Unexpected cyberco value in scripts/ThreatPatrols/Autossh code" 21 | echo 22 | exit 1 23 | fi 24 | 25 | # Increment the PLUGIN_VERSION minor-version value in the Makefile 26 | if [ -f "Makefile" ]; then 27 | 28 | plugin_revision_main=$(cat Makefile | grep ^PLUGIN_VERSION= | cut -d'=' -f2 | tr -d ' ' | rev | cut -d'.' -f2- | rev) 29 | plugin_revision_minor=$(($(cat Makefile | grep ^PLUGIN_VERSION= | cut -d'=' -f2 | tr -d ' ' | rev | cut -d'.' -f1 | rev)+1)) 30 | sed -i "s/^PLUGIN_VERSION=.*/PLUGIN_VERSION= ${plugin_revision_main}.${plugin_revision_minor}/" "Makefile" 31 | git add Makefile 32 | 33 | echo "PLUGIN_VERSION= ${plugin_revision_main}.${plugin_revision_minor}" 34 | fi 35 | 36 | fi 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.py[cod] 3 | *.egg-info 4 | *.egg 5 | 6 | venv*/ 7 | .venv*/ 8 | .idea/ 9 | .pytest_cache/ 10 | __pycache__/ 11 | .pytest*/ 12 | 13 | dev/ 14 | samples/ 15 | 16 | dist/ 17 | build/ 18 | packages/ 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Threat Patrols Pty Ltd 2 | Copyright (c) 2018 Verb Networks Pty Ltd 3 | Copyright (c) 2018 Nicholas de Jong 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, 7 | are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 23 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PLUGIN_NAME= autossh 2 | PLUGIN_VERSION= 0.2.42 3 | PLUGIN_COMMENT= Create and automatically persist SSH based tunnels using autossh 4 | PLUGIN_MAINTAINER= contact@threatpatrols.com 5 | PLUGIN_WWW= https://documentation.threatpatrols.com/opnsense/plugins/autossh/ 6 | PLUGIN_DEPENDS= autossh 7 | 8 | PLUGIN_PREFIX= os- 9 | PLUGIN_SUFFIX= 10 | PLUGIN_DEVEL= no 11 | 12 | _VERSION_UPDATE!= echo "__version__ = \"${PLUGIN_VERSION}\"" > src/opnsense/scripts/ThreatPatrols/Autossh/autossh/__version__.py 13 | 14 | .include "../../Mk/plugins.mk" 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Autossh for OPNsense 2 | 3 | The Autossh plugin for OPNsense is a wrapper for the autossh system-package that 4 | allows for establishing persistent reliable SSH tunnels with remote hosts. It can 5 | be used to solve a wide range of connection challenges through the use of TCP 6 | port-forwards. 7 | 8 | ## Documentation 9 | * https://documentation.threatpatrols.com/opnsense/plugins/autossh/ 10 | 11 | ## Issues 12 | * https://github.com/threatpatrols/opnsense-plugin-autossh/issues 13 | 14 | ## Source 15 | * https://github.com/threatpatrols/opnsense-plugin-autossh 16 | 17 | ## Copyright 18 | * Copyright © 2022 Threat Patrols Pty Ltd <contact@threatpatrols.com> 19 | * Copyright © 2018 Verb Networks Pty Ltd <contact@verbnetworks.com> 20 | * Copyright © 2018 Nicholas de Jong <me@nicholasdejong.com> 21 | 22 | All rights reserved. 23 | 24 | ## License 25 | * BSD-2-Clause - see LICENSE file for full details. 26 | -------------------------------------------------------------------------------- /pkg-descr: -------------------------------------------------------------------------------- 1 | The Autossh plugin for OPNsense is a wrapper for the autossh system-package that allows for establishing persistent reliable SSH tunnels with remote hosts. It can be used to solve a wide range of connection challenges through the (sometimes creative) use of TCP port-forwards. 2 | 3 | Autossh tunnels can be used to quickly solve a wide range of challenges all over SSH, without the need for VPN clients etc: 4 | * Provide reverse-remote access to a site that has no public addresses, such as when ISPs use NAT. 5 | * Ensure redundant multipath reverse-remote access via both primary and secondary connections via interface binding. 6 | * Create your own "privacy" VPN system for local network users using a SOCKS proxy (ssh-dynamic-forward) to a remote system. 7 | * Provide local network access to remote system services such as SMTP relays or another remote TCP services. 8 | * Provide reverse-remote access to local network services such local RDP services. 9 | 10 | WWW: https://documentation.threatpatrols.com/opnsense/plugins/autossh/ 11 | -------------------------------------------------------------------------------- /src/etc/inc/plugins.inc.d/autossh.inc: -------------------------------------------------------------------------------- 1 | 5 | Copyright (c) 2018 Verb Networks Pty Ltd 6 | Copyright (c) 2018 Nicholas de Jong 7 | All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without modification, 10 | are permitted provided that the following conditions are met: 11 | 12 | 1. Redistributions of source code must retain the above copyright notice, this 13 | list of conditions and the following disclaimer. 14 | 15 | 2. Redistributions in binary form must reproduce the above copyright notice, 16 | this list of conditions and the following disclaimer in the documentation 17 | and/or other materials provided with the distribution. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 23 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 26 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | use ThreatPatrols\Autossh\Autossh; 32 | 33 | function autossh_services() 34 | { 35 | $services = array(); 36 | $model = new Autossh(); 37 | $node = $model->getNodeByReference('tunnels.tunnel'); 38 | 39 | if ($node != null) { 40 | foreach($node->getNodes() as $tunnel_uuid=>$tunnel) { 41 | if((int)$tunnel['enabled'] > 0) { 42 | $description = $tunnel['user'] .'@'. $tunnel['hostname']; 43 | if(!empty($tunnel['port'])) { 44 | $description = $description.':'.$tunnel['port']; 45 | } 46 | if (strlen($description) > 32) 47 | $description = substr($description, 0, 29) . '...'; 48 | $services[] = array( 49 | 'id' => $tunnel_uuid, 50 | 'name' => 'autossh', 51 | 'pidfile' => '/var/run/autossh.'.$tunnel_uuid.'.pid', 52 | 'description' => htmlspecialchars($description), 53 | 'configd' => array( 54 | 'start' => array('autossh start_tunnel '.$tunnel_uuid), 55 | 'stop' => array('autossh stop_tunnel '.$tunnel_uuid), 56 | 'restart' => array('autossh restart_tunnel '.$tunnel_uuid), 57 | ), 58 | ); 59 | } 60 | } 61 | } 62 | return $services; 63 | } 64 | 65 | function autossh_syslog() 66 | { 67 | $logfacilities = array(); 68 | $logfacilities['autossh'] = array( 69 | 'facility' => array('autossh', 'autosshd', '/usr/local/bin/ssh'), 70 | ); 71 | return $logfacilities; 72 | } 73 | -------------------------------------------------------------------------------- /src/etc/rc.d/opnsense-autossh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # $FreeBSD$ 4 | # 5 | 6 | # PROVIDE: autossh 7 | 8 | # Add the following /etc/rc.conf.d/autossh entry to enable autossh: 9 | # autossh_enable="YES" 10 | 11 | 12 | . /etc/rc.subr 13 | 14 | name=autossh 15 | rcvar=${name}_enable 16 | 17 | load_rc_config $name 18 | autossh_enable=${autossh_enable:-"NO"} 19 | autossh_loglevel=${autossh_loglevel:-1} 20 | autossh_monitor_port_min=${autossh_monitor_port_min:-0} 21 | autossh_monitor_port_max=${autossh_monitor_port_max:-0} 22 | autossh_monitor_poll_interval=${autossh_monitor_poll_interval:-60} 23 | autossh_configfile=${autossh_configfile:-"/usr/local/etc/autossh/autossh.conf"} 24 | 25 | autossh_flags="-N -y -F ${autossh_configfile}" 26 | command=/usr/local/bin/autossh 27 | pidfile=/dev/null 28 | 29 | # all tunnels commands 30 | start_cmd="autossh_start_tunnels_cmd" 31 | stop_cmd="autossh_stop_tunnels_cmd" 32 | status_cmd="autossh_status_tunnels_cmd" 33 | restart_cmd="autossh_restart_tunnels_cmd" 34 | 35 | # per tunnel commands 36 | start_tunnel_cmd="autossh_start_tunnel_cmd" 37 | stop_tunnel_cmd="autossh_stop_tunnel_cmd" 38 | status_tunnel_cmd="autossh_status_tunnel_cmd" 39 | restart_tunnel_cmd="autossh_restart_tunnel_cmd" 40 | 41 | extra_commands="start_tunnel stop_tunnel status_tunnel restart_tunnel" 42 | 43 | 44 | # ============================================================================= 45 | 46 | autossh_start_tunnels_cmd() { 47 | for tunnel_uuid in ${tunnel_uuids}; do 48 | autossh_start_tunnel_cmd ${tunnel_uuid} 49 | done 50 | } 51 | 52 | autossh_stop_tunnels_cmd() { 53 | for tunnel_uuid in ${tunnel_uuids}; do 54 | autossh_stop_tunnel_cmd ${tunnel_uuid} 55 | done 56 | } 57 | 58 | autossh_restart_tunnels_cmd() { 59 | for tunnel_uuid in ${tunnel_uuids}; do 60 | autossh_restart_tunnel_cmd ${tunnel_uuid} 61 | done 62 | } 63 | 64 | autossh_status_tunnels_cmd() { 65 | for tunnel_uuid in ${tunnel_uuids}; do 66 | autossh_status_tunnel_cmd ${tunnel_uuid} 67 | done 68 | } 69 | 70 | # ============================================================================= 71 | 72 | autossh_start_tunnel_cmd() { 73 | tunnel_uuid=${1} 74 | if [ -z ${tunnel_uuid} ]; then 75 | echo "Fail: no tunnel_uuid supplied to start_tunnel command." 76 | exit 1 77 | fi 78 | 79 | __autossh_syslog_helper 80 | 81 | pidfile="/var/run/autossh.${tunnel_uuid}.pid" 82 | autossh_random_port=$(__autossh_random_port_helper ${autossh_monitor_port_min} ${autossh_monitor_port_max}) 83 | 84 | export AUTOSSH_GATETIME=0 85 | export AUTOSSH_DEBUG=${autossh_loglevel} 86 | export AUTOSSH_POLL=${autossh_monitor_poll_interval} 87 | export AUTOSSH_PORT=${autossh_random_port} 88 | 89 | if [ ! -e ${pidfile} ]; then 90 | echo "Starting ${name} tunnel ${tunnel_uuid} ..." 91 | daemon -S -P ${pidfile} -r -t "autossh-daemon.${tunnel_uuid}" -T "autosshd" -u "root" ${command} ${autossh_flags} ${tunnel_uuid} 92 | sleep 1 93 | else 94 | if [ $(ps -o pid= -p `cat ${pidfile}` | wc -l) -gt 0 ]; then 95 | echo "${name} tunnel ${tunnel_uuid} already running pid:$(cat ${pidfile})" 96 | else 97 | echo "Starting ${name} tunnel ${tunnel_uuid} (removing old pid file) ..." 98 | rm -f "${pidfile}" 99 | daemon -S -P ${pidfile} -r -t "autossh-daemon.${tunnel_uuid}" -T "autosshd" -u "root" ${command} ${autossh_flags} ${tunnel_uuid} 100 | sleep 1 101 | fi 102 | fi 103 | if [ ! -e ${pidfile} ]; then 104 | echo "Fail: unable to start ${name} tunnel ${tunnel_uuid} ..." 105 | fi 106 | } 107 | 108 | autossh_stop_tunnel_cmd() { 109 | tunnel_uuid=${1} 110 | if [ -z ${tunnel_uuid} ]; then 111 | echo "Fail: no tunnel_uuid supplied to stop_tunnel command." 112 | exit 1 113 | fi 114 | pidfile="/var/run/autossh.${tunnel_uuid}.pid" 115 | infofile="/var/run/autossh.${tunnel_uuid}.info" 116 | if [ -e ${pidfile} ] && [ $(ps -o pid= -p `cat ${pidfile}` | wc -l) -gt 0 ]; then 117 | echo "Stopping ${name} tunnel ${tunnel_uuid} ..." 118 | if [ -e ${pidfile} ]; then 119 | kill $(cat ${pidfile}) 120 | fi 121 | rm -f "${pidfile}" 122 | rm -f "${infofile}" 123 | sleep 1 124 | else 125 | echo "${name} tunnel ${tunnel_uuid} is not running?" 126 | fi 127 | } 128 | 129 | autossh_restart_tunnel_cmd() { 130 | tunnel_uuid=${1} 131 | if [ -z ${tunnel_uuid} ]; then 132 | echo "Fail: no tunnel_uuid supplied to restart_tunnel command." 133 | exit 1 134 | fi 135 | pidfile="/var/run/autossh.${tunnel_uuid}.pid" 136 | if [ -f ${pidfile} ]; then 137 | autossh_stop_tunnel_cmd ${tunnel_uuid} 138 | fi 139 | autossh_start_tunnel_cmd ${tunnel_uuid} 140 | } 141 | 142 | autossh_status_tunnel_cmd() { 143 | tunnel_uuid=${1} 144 | if [ -z ${tunnel_uuid} ]; then 145 | echo "Fail: no tunnel_uuid supplied to status_tunnel command." 146 | exit 1 147 | fi 148 | pidfile="/var/run/autossh.${tunnel_uuid}.pid" 149 | if [ -e ${pidfile} ] && [ $(ps -o pid= -p `cat ${pidfile}` | wc -l) -gt 0 ]; then 150 | echo "${name} tunnel ${tunnel_uuid} is running pid:$(cat ${pidfile})" 151 | else 152 | echo "${name} tunnel ${tunnel_uuid} is NOT running" 153 | fi 154 | } 155 | 156 | __autossh_random_port_helper() { 157 | min_random_port=${1} 158 | max_random_port=${2} 159 | continue_port_search=1 160 | autossh_random_port=0 161 | while [ ${continue_port_search} -gt 0 ]; do 162 | autossh_random_port=$(expr `jot -r 1 0 $(expr $(expr ${max_random_port} - ${min_random_port}) \/ 2)` \* 2 + ${min_random_port}) 163 | if [ $(netstat -anW -p tcp | grep LISTEN | grep "\.${random_port} " | wc -l | tr -d ' ') -eq 0 ]; then 164 | continue_port_search=0 165 | fi 166 | done 167 | echo ${autossh_random_port} 168 | } 169 | 170 | __autossh_syslog_helper() { 171 | current_syslog_logfile="/var/log/autossh/autossh_$(date +%Y%m%d).log" 172 | 173 | if [ ! -d /var/log/autossh ]; then 174 | mkdir -p -m 700 /var/log/autossh 175 | fi 176 | 177 | if [ ! -e "${current_syslog_logfile}" ]; then 178 | touch "${current_syslog_logfile}" 179 | chmod 600 "${current_syslog_logfile}" 180 | fi 181 | 182 | chown -R root:wheel /var/log/autossh 183 | 184 | if [ ! -e "/var/log/autossh/latest.log" ]; then 185 | configctl syslog archive 186 | if [ ! -s "/var/log/autossh/latest.log" ]; then 187 | configctl syslog restart 188 | fi 189 | fi 190 | } 191 | 192 | # ============================================================================= 193 | 194 | # load the tunnel uuids first 195 | tunnel_uuids=$(cat ${autossh_configfile} | grep '^Host ' | cut -d' ' -f2) 196 | 197 | # invoke run_rc_command 198 | if [ $(echo ${1} | grep "_tunnel" | wc -l | tr -d ' ') -eq 0 ]; then 199 | run_rc_command "${1}" 200 | else 201 | run_rc_command "${1}" "${2}" 202 | fi 203 | -------------------------------------------------------------------------------- /src/opnsense/mvc/app/controllers/ThreatPatrols/Autossh/Api/AutosshApiControllerBase.php: -------------------------------------------------------------------------------- 1 | 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | 2. Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 21 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | namespace ThreatPatrols\Autossh\Api; 30 | 31 | use OPNsense\Core\Backend; 32 | use OPNsense\Core\Config; 33 | use OPNsense\Base\ApiControllerBase; 34 | 35 | class AutosshApiControllerBase extends ApiControllerBase 36 | { 37 | public function validate($model, $node = null, $reference = null) 38 | { 39 | $result = array('status' => 'fail', 'validations' => array()); 40 | $validation_messages = $model->performValidation(); 41 | foreach ($validation_messages as $field => $message) { 42 | if ($node != null) { 43 | $index = str_replace($node->__reference, $reference, $message->getField()); 44 | $result['validations'][$index] = $message->getMessage(); 45 | } else { 46 | $result['validations'][$message->getField()] = $message->getMessage(); 47 | } 48 | } 49 | return $result; 50 | } 51 | 52 | public function configctlAction($action, $uuid) 53 | { 54 | $backend = new Backend(); 55 | $configd_run = sprintf('autossh %s %s', escapeshellarg($action), escapeshellarg($uuid)); 56 | return trim($backend->configdRun($configd_run)); 57 | } 58 | 59 | public function save($model, $node = null, $reference = null) 60 | { 61 | $result = $this->validate($model, $node, $reference); 62 | if (0 === count($result['validations'])) { 63 | $model->serializeToConfig(); 64 | Config::getInstance()->save(); 65 | return $this->doConfigUpdates("Data saved"); 66 | } 67 | return $result; 68 | } 69 | 70 | public function doConfigUpdates($message = null) 71 | { 72 | $backend = new Backend(); 73 | 74 | // reload templates first 75 | $backend_response = trim($backend->configdRun('template reload ThreatPatrols/Autossh')); 76 | if (strtoupper($backend_response) !== 'OK') { 77 | $error_message = "Error while reloading autossh template files, review configd logs for more information"; 78 | syslog(LOG_ERR, $error_message); 79 | return array('status' => 'fail', 'message' => $error_message); 80 | } 81 | 82 | // render the autossh files 83 | $backend_response = @json_decode(trim($backend->configdRun('autossh config_helper')), true); 84 | if (empty($backend_response) || !isset($backend_response['status'])) { 85 | $error_message = "Error while performing autossh config_helper, review configd logs for more information"; 86 | syslog(LOG_ERR, $error_message); 87 | return array('status' => 'fail', 'message' => $error_message); 88 | } 89 | 90 | if (empty($message)) { 91 | $message = "Configuration template and configuration helper completed"; 92 | } 93 | return array('status' => 'success', 'message' => $message); 94 | } 95 | 96 | public function afterExecuteRoute($dispatcher) 97 | { 98 | /** 99 | * In the limited situation of an "info" action with "html" we catch the regular 100 | * afterExecuteRoute() to prevent htmlspecialchars() being universally applied. 101 | */ 102 | if ($dispatcher->getActionName() === "info") { 103 | $data = $dispatcher->getReturnedValue(); 104 | if (is_array($data) && isset($data['html']) && true === $data['html']) { 105 | $this->response->setContentType('application/json', 'UTF-8'); 106 | $this->response->setContent(json_encode($data)); 107 | return $this->response->send(); 108 | } 109 | } 110 | // all other situations get passed to the parent as usual. 111 | return parent::afterExecuteRoute($dispatcher); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/opnsense/mvc/app/controllers/ThreatPatrols/Autossh/Api/ConnectionsController.php: -------------------------------------------------------------------------------- 1 | 5 | Copyright (c) 2018 Verb Networks Pty Ltd 6 | Copyright (c) 2018 Nicholas de Jong 7 | All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without modification, 10 | are permitted provided that the following conditions are met: 11 | 12 | 1. Redistributions of source code must retain the above copyright notice, this 13 | list of conditions and the following disclaimer. 14 | 15 | 2. Redistributions in binary form must reproduce the above copyright notice, 16 | this list of conditions and the following disclaimer in the documentation 17 | and/or other materials provided with the distribution. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 23 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 26 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | namespace ThreatPatrols\Autossh\Api; 32 | 33 | use OPNsense\Base\ApiControllerBase; 34 | use OPNsense\Base\UIModelGrid; 35 | use ThreatPatrols\Autossh\Autossh; 36 | use ThreatPatrols\Autossh\Api\AutosshApiControllerBase; 37 | 38 | class ConnectionsController extends AutosshApiControllerBase 39 | { 40 | public function reloadAction() 41 | { 42 | $response = array('status' => 'fail', 'message' => 'Invalid request'); 43 | if ($this->request->isPost()) { 44 | return $this->doConfigUpdates("Connections reloaded"); 45 | } 46 | return $response; 47 | } 48 | 49 | public function statusAction() 50 | { 51 | $response = array('status' => 'fail', 'message' => 'Invalid request'); 52 | if ($this->request->isPost() && $this->request->hasPost('id')) { 53 | $backend_result = $this->configctlAction('status_tunnel', $this->request->getPost('id')); 54 | if (false === strpos($backend_result, ' not running')) { 55 | return array('status' => 'running'); 56 | } else { 57 | return array('status' => 'stopped'); 58 | } 59 | } 60 | return $response; 61 | } 62 | 63 | public function startAction() 64 | { 65 | $response = array('status' => 'fail', 'message' => 'Invalid request'); 66 | if ($this->request->isPost() && $this->request->hasPost('id')) { 67 | $backend_result = $this->configctlAction('start_tunnel', $this->request->getPost('id')); 68 | if (false !== strpos($backend_result, 'Starting autossh tunnel ')) { 69 | return array('status' => 'success', 'message' => $backend_result); 70 | } else { 71 | syslog(LOG_ERR, $backend_result); 72 | return array('status' => 'fail', 'message' => $backend_result); 73 | } 74 | } 75 | return $response; 76 | } 77 | 78 | public function restartAction() 79 | { 80 | $response = array('status' => 'fail', 'message' => 'Invalid request'); 81 | if ($this->request->isPost() && $this->request->hasPost('id')) { 82 | $backend_result = $this->configctlAction('restart_tunnel', $this->request->getPost('id')); 83 | if (false !== strpos($backend_result, 'Starting autossh tunnel ')) { 84 | return array('status' => 'success', 'message' => $backend_result); 85 | } else { 86 | syslog(LOG_ERR, $backend_result); 87 | return array('status' => 'fail', 'message' => $backend_result); 88 | } 89 | } 90 | return $response; 91 | } 92 | 93 | public function stopAction() 94 | { 95 | $response = array('status' => 'fail', 'message' => 'Invalid request'); 96 | if ($this->request->isPost() && $this->request->hasPost('id')) { 97 | $backend_result = $this->configctlAction('stop_tunnel', $this->request->getPost('id')); 98 | if (false !== strpos($backend_result, 'Stopping autossh tunnel ')) { 99 | return array('status' => 'success', 'message' => $backend_result); 100 | } else { 101 | syslog(LOG_ERR, $backend_result); 102 | return array('status' => 'fail', 'message' => $backend_result); 103 | } 104 | } 105 | return $response; 106 | } 107 | 108 | public function listStatusAction() 109 | { 110 | $response = array( 111 | 'current' => 1, 112 | 'rowCount' => null, 113 | 'rows' => array(), 114 | 'total' => null 115 | ); 116 | 117 | if ($this->request->isGet()) { 118 | $model = new Autossh(); 119 | $grid = new UIModelGrid($model->tunnels->tunnel); 120 | 121 | $grid_data = $grid->fetchBindRequest($this->request, array( 122 | 'enabled', 'user', 'hostname', 'port', 'bind_interface', 'ssh_key', 123 | 'local_forward', 'remote_forward', 'dynamic_forward', 'tunnel_device' 124 | ), 'hostname'); 125 | 126 | foreach ($grid_data['rows'] as $tunnel) { 127 | $connection = $tunnel['user'] . '@' . $tunnel['hostname']; 128 | if (!empty($tunnel['port'])) { 129 | $connection .= ':' . $tunnel['port']; 130 | } 131 | 132 | $forward_data = array( 133 | 'local' => $tunnel['local_forward'], 134 | 'dynamic' => $tunnel['dynamic_forward'], 135 | 'remote' => $tunnel['remote_forward'], 136 | 'tunnel' => $tunnel['tunnel_device'] 137 | ); 138 | 139 | $backend_result = @json_decode($this->configctlAction("connection_status", $tunnel['uuid']), true); 140 | $status_data = array('enabled' => null); 141 | 142 | if (isset($backend_result['status']) && $backend_result['status'] === 'success') { 143 | $last_healthy = (int)strtotime($backend_result['data']['last_healthy']); 144 | if (empty($last_healthy)) { 145 | if (!empty($backend_result['data']['uptime'])) { 146 | $last_healthy = -1; 147 | } else { 148 | $last_healthy = null; 149 | } 150 | } else { 151 | $last_healthy = (int)(time() - $last_healthy); 152 | if ($last_healthy > $backend_result['data']['uptime']) { 153 | $last_healthy = -1; 154 | } 155 | } 156 | 157 | $status_data = array( 158 | 'enabled' => $backend_result['data']['enabled'], 159 | 'uptime' => $backend_result['data']['uptime'], 160 | 'last_healthy' => $last_healthy, 161 | 'starts' => $backend_result['data']['starts'], 162 | ); 163 | } 164 | 165 | $response['rows'][] = array( 166 | 'uuid' => $tunnel['uuid'], 167 | 'connection' => $connection, 168 | 'bind_interface' => $tunnel['bind_interface'], 169 | 'forwards' => $forward_data, 170 | 'ssh_key' => $tunnel['ssh_key'], 171 | 'status' => $status_data 172 | ); 173 | } 174 | } 175 | 176 | $response['rowCount'] = count($response['rows']); 177 | $response['total'] = count($response['rows']); 178 | 179 | return $response; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/opnsense/mvc/app/controllers/ThreatPatrols/Autossh/Api/KeysController.php: -------------------------------------------------------------------------------- 1 | 5 | Copyright (c) 2018 Verb Networks Pty Ltd 6 | Copyright (c) 2018 Nicholas de Jong 7 | All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without modification, 10 | are permitted provided that the following conditions are met: 11 | 12 | 1. Redistributions of source code must retain the above copyright notice, this 13 | list of conditions and the following disclaimer. 14 | 15 | 2. Redistributions in binary form must reproduce the above copyright notice, 16 | this list of conditions and the following disclaimer in the documentation 17 | and/or other materials provided with the distribution. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 23 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 26 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | namespace ThreatPatrols\Autossh\Api; 32 | 33 | use OPNsense\Core\Config; 34 | use OPNsense\Base\UIModelGrid; 35 | use ThreatPatrols\Autossh\Autossh; 36 | use ThreatPatrols\Autossh\Api\AutosshApiControllerBase; 37 | 38 | class KeysController extends AutosshApiControllerBase 39 | { 40 | public function searchAction() 41 | { 42 | $this->sessionClose(); // close out long running actions 43 | $model = new Autossh(); 44 | $grid = new UIModelGrid($model->keys->key); 45 | 46 | $grid_data = $grid->fetchBindRequest( 47 | $this->request, 48 | array('name', 'description', 'type', 'key_fingerprint', 'timestamp'), 49 | 'name' 50 | ); 51 | 52 | foreach ($grid_data['rows'] as $row_index => $row_data) { 53 | foreach ($row_data as $key => $value) { 54 | if ($key === 'timestamp') { 55 | $grid_data['rows'][$row_index][$key] = date('Y-m-d H:i:s', $value); 56 | } elseif ($key === 'key_fingerprint') { 57 | $fingerprint_elements = explode(' ', $value); 58 | $grid_data['rows'][$row_index][$key] = $fingerprint_elements[1]; 59 | } 60 | } 61 | } 62 | return $grid_data; 63 | } 64 | 65 | public function getAction($uuid = null) 66 | { 67 | $model = new Autossh(); 68 | if ($uuid != null) { 69 | $node = $model->getNodeByReference('keys.key.' . $uuid); 70 | if ($node != null) { 71 | $data = $node->getNodes(); 72 | // munge the data a little bit making it easier to use 73 | $data['timestamp'] = date('Y-m-d H:i:s', $data['timestamp']); 74 | $fingerprint_elements = explode(' ', $data['key_fingerprint']); 75 | $data['key_fingerprint'] = $fingerprint_elements[1]; 76 | unset($data['key_private']); 77 | return array('key' => $data); 78 | } 79 | } else { 80 | $node = $model->keys->key->add(); 81 | return array('key' => $node->getNodes()); 82 | } 83 | return array(); 84 | } 85 | 86 | public function infoAction($uuid = null) 87 | { 88 | $ssh_key_restrictions = 'command="",no-agent-forwarding,no-pty,no-user-rc,no-X11-forwarding'; 89 | $info = array( 90 | 'title' => 'SSH public-key with shell-prevention restrictions for tunnel remote', 91 | 'message' => 'Unknown ssh-key', 92 | ); 93 | if ($uuid != null) { 94 | $model = new Autossh(); 95 | $node = $model->getNodeByReference('keys.key.' . $uuid); 96 | if ($node != null) { 97 | $node_data = $node->getNodes(); 98 | $key_public = base64_decode($node_data['key_public']); 99 | 100 | // replace host_id comment with this key uuid 101 | $key_public = preg_replace('/host_id:.*?$/', 'autossh_key_id:' . $uuid, $key_public); 102 | 103 | // prepend ssh-key restrictions 104 | $key_public = $ssh_key_restrictions . ' ' . $key_public; 105 | 106 | $info['html'] = true; // required for afterExecuteRoute() trap 107 | $info['message'] = '

' . $key_public . '

'; 108 | } 109 | } 110 | return $info; 111 | } 112 | 113 | public function setAction($uuid) 114 | { 115 | $response = array( 116 | 'status' => 'fail', 117 | 'message' => 'Invalid request' 118 | ); 119 | if ($this->request->isPost() && $this->request->hasPost('key')) { 120 | $model = new Autossh(); 121 | if ($uuid != null) { 122 | $node = $model->getNodeByReference('keys.key.' . $uuid); 123 | if ($node != null) { 124 | $post_data = $this->request->getPost('key'); 125 | unset($post_data['type']); 126 | $node->setNodes($post_data); 127 | return $this->save($model, $node, 'key'); 128 | } 129 | } 130 | } 131 | return $response; 132 | } 133 | 134 | public function addAction() 135 | { 136 | $response = array( 137 | 'status' => 'fail', 138 | 'message' => 'Invalid request' 139 | ); 140 | if ($this->request->isPost() && $this->request->hasPost('key')) { 141 | $model = new Autossh(); 142 | $node = $model->keys->key->add(); 143 | $post_data = $this->request->getPost('key'); 144 | $node->setNodes($post_data); 145 | 146 | $validate = $this->validate($model, $node, 'key'); 147 | if (count($validate['validations']) == 0) { 148 | $backend_response = @json_decode($this->configctlAction("key_gen", $post_data['type']), true); 149 | if (empty($backend_response)) { 150 | $error_message = "Error calling autossh key_gen via configd"; 151 | syslog(LOG_ERR, $error_message); 152 | return array('status' => 'fail', 'message' => $error_message); 153 | } elseif ($backend_response['status'] === 'success') { 154 | $node->setNodes(array_merge($post_data, $backend_response['data'])); 155 | return $this->save($model, $node, 'key'); 156 | } 157 | } else { 158 | return array( 159 | 'status' => 'fail', 160 | 'validations' => $validate['validations'], 161 | 'message' => 'Validation errors' 162 | ); 163 | } 164 | } 165 | return $response; 166 | } 167 | 168 | public function delAction($uuid = null) 169 | { 170 | $response = array('status' => 'fail', 'message' => 'Invalid request'); 171 | if ($this->request->isPost()) { 172 | $model = new Autossh(); 173 | if ($uuid != null) { 174 | if ($model->keys->key->del($uuid)) { 175 | $model->serializeToConfig(); 176 | Config::getInstance()->save(); 177 | return array('status' => 'success', 'message' => 'Okay, item deleted'); 178 | } else { 179 | return array('status' => 'fail', 'message' => 'Item not found, nothing deleted'); 180 | } 181 | } 182 | } 183 | return $response; 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/opnsense/mvc/app/controllers/ThreatPatrols/Autossh/Api/TunnelsController.php: -------------------------------------------------------------------------------- 1 | 5 | Copyright (c) 2018 Verb Networks Pty Ltd 6 | Copyright (c) 2018 Nicholas de Jong 7 | All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without modification, 10 | are permitted provided that the following conditions are met: 11 | 12 | 1. Redistributions of source code must retain the above copyright notice, this 13 | list of conditions and the following disclaimer. 14 | 15 | 2. Redistributions in binary form must reproduce the above copyright notice, 16 | this list of conditions and the following disclaimer in the documentation 17 | and/or other materials provided with the distribution. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 23 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 26 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | namespace ThreatPatrols\Autossh\Api; 32 | 33 | use OPNsense\Core\Config; 34 | use OPNsense\Base\UIModelGrid; 35 | use ThreatPatrols\Autossh\Autossh; 36 | use ThreatPatrols\Autossh\Api\AutosshApiControllerBase; 37 | 38 | class TunnelsController extends AutosshApiControllerBase 39 | { 40 | public function searchAction() 41 | { 42 | $this->sessionClose(); // close out long running actions 43 | $model = new Autossh(); 44 | $grid = new UIModelGrid($model->tunnels->tunnel); 45 | 46 | $grid_data = $grid->fetchBindRequest( 47 | $this->request, 48 | array( 49 | 'enabled', 'description', 'user', 'hostname', 'port', 'bind_interface', 'ssh_key', 50 | 'local_forward', 'remote_forward', 'dynamic_forward' 51 | ), 52 | 'hostname' 53 | ); 54 | 55 | if (isset($grid_data['rows'])) { 56 | foreach ($grid_data['rows'] as $index => $tunnel) { 57 | $grid_data['rows'][$index]['connection'] = $tunnel['user'] . '@' . $tunnel['hostname']; 58 | if (!empty($tunnel['port'])) { 59 | $grid_data['rows'][$index]['connection'] = 60 | $grid_data['rows'][$index]['connection'] . ':' . $tunnel['port']; 61 | } 62 | } 63 | } 64 | return $grid_data; 65 | } 66 | 67 | public function getAction($uuid = null) 68 | { 69 | $model = new Autossh(); 70 | if ($uuid != null) { 71 | $node = $model->getNodeByReference('tunnels.tunnel.' . $uuid); 72 | if ($node != null) { 73 | $data = array('tunnel' => $node->getNodes()); 74 | return $data; 75 | } 76 | } else { 77 | $node = $model->tunnels->tunnel->add(); 78 | $data = array('tunnel' => $node->getNodes()); 79 | $data['tunnel']['known_host'] = 'new connection, no known host value'; 80 | return $data; 81 | } 82 | return array(); 83 | } 84 | 85 | public function infoAction($uuid = null) 86 | { 87 | $info = array( 88 | 'title' => 'SSH server known host keys', 89 | 'message' => null, 90 | ); 91 | if ($uuid != null) { 92 | $response = json_decode($this->configctlAction("host_keys", $uuid), true); 93 | if ($response['status'] === 'success' && isset($response['data']) && count($response['data']) > 0) { 94 | $info['html'] = true; // required for afterExecuteRoute() trap 95 | $info['message'] = ''; 96 | foreach ($response['data'] as $key_value) { 97 | $info['message'] = $info['message'] . '

' . htmlspecialchars($key_value) . '

'; 98 | } 99 | } else { 100 | $info['message'] = $response['message']; 101 | } 102 | } 103 | return $info; 104 | } 105 | 106 | public function setAction($uuid = null) 107 | { 108 | $response = array( 109 | 'status' => 'fail', 110 | 'message' => 'Invalid request' 111 | ); 112 | if ($this->request->isPost() && $this->request->hasPost('tunnel')) { 113 | $model = new Autossh(); 114 | if ($uuid !== null) { 115 | $node = $model->getNodeByReference('tunnels.tunnel.' . $uuid); 116 | if ($node !== null) { 117 | $post_data = $this->request->getPost('tunnel'); 118 | $node->setNodes($post_data); 119 | $response = $this->save($model, $node, 'tunnel'); 120 | if (1 === (int)$post_data['enabled']) { 121 | $this->restartTunnel($uuid); 122 | } else { 123 | $this->stopTunnel($uuid); 124 | } 125 | return $response; 126 | } 127 | } 128 | } 129 | return $response; 130 | } 131 | 132 | public function addAction() 133 | { 134 | $response = array( 135 | 'status' => 'fail', 136 | 'message' => 'Invalid request' 137 | ); 138 | if ($this->request->isPost() && $this->request->hasPost('tunnel')) { 139 | $model = new Autossh(); 140 | $node = $model->tunnels->tunnel->add(); 141 | $post_data = $this->request->getPost('tunnel'); 142 | $node->setNodes($post_data); 143 | 144 | $validate = $this->validate($model, $node, 'tunnel'); 145 | if (0 === count($validate['validations'])) { 146 | return $this->save($model, $node, 'tunnel'); 147 | } else { 148 | return array( 149 | 'status' => 'fail', 150 | 'validations' => $validate['validations'], 151 | 'message' => 'Validation errors' 152 | ); 153 | } 154 | } 155 | return $response; 156 | } 157 | 158 | public function delAction($uuid = null) 159 | { 160 | $response = array( 161 | 'status' => 'fail', 162 | 'message' => 'Invalid request' 163 | ); 164 | if ($this->request->isPost()) { 165 | $model = new Autossh(); 166 | if ($uuid != null) { 167 | $this->stopTunnel($uuid); 168 | if ($model->tunnels->tunnel->del($uuid)) { 169 | $model->serializeToConfig(); 170 | Config::getInstance()->save(); 171 | return $this->doConfigUpdates("Item deleted"); 172 | } else { 173 | return array('status' => 'fail', 'message' => 'Item not found, nothing deleted'); 174 | } 175 | } 176 | } 177 | return $response; 178 | } 179 | 180 | public function toggleAction($uuid = null) 181 | { 182 | $response = array( 183 | 'status' => 'fail', 184 | 'message' => 'Invalid request' 185 | ); 186 | if ($this->request->isPost()) { 187 | $model = new Autossh(); 188 | if ($uuid != null) { 189 | $node = $model->getNodeByReference('tunnels.tunnel.' . $uuid); 190 | if (!empty($node)) { 191 | $node_data = $node->getNodes(); 192 | $toggle_data = array( 193 | 'enabled' => ((int)$node_data['enabled'] > 0 ? '0' : '1') 194 | ); 195 | $node->setNodes($toggle_data); 196 | $response = $this->save($model, $node, 'tunnel'); 197 | if (1 === (int)$toggle_data['enabled']) { 198 | $this->startTunnel($uuid); 199 | } else { 200 | $this->stopTunnel($uuid); 201 | } 202 | return $response; 203 | } 204 | } 205 | } 206 | return $response; 207 | } 208 | 209 | public function startTunnel($uuid) 210 | { 211 | return $this->configctlAction("start_tunnel", $uuid); 212 | } 213 | 214 | public function restartTunnel($uuid) 215 | { 216 | return $this->configctlAction("restart_tunnel", $uuid); 217 | } 218 | 219 | public function stopTunnel($uuid) 220 | { 221 | return $this->configctlAction("stop_tunnel", $uuid); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/opnsense/mvc/app/controllers/ThreatPatrols/Autossh/ConnectionsController.php: -------------------------------------------------------------------------------- 1 | 5 | Copyright (c) 2018 Verb Networks Pty Ltd 6 | Copyright (c) 2018 Nicholas de Jong 7 | All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without modification, 10 | are permitted provided that the following conditions are met: 11 | 12 | 1. Redistributions of source code must retain the above copyright notice, this 13 | list of conditions and the following disclaimer. 14 | 15 | 2. Redistributions in binary form must reproduce the above copyright notice, 16 | this list of conditions and the following disclaimer in the documentation 17 | and/or other materials provided with the distribution. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 23 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 26 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | namespace ThreatPatrols\Autossh; 32 | 33 | class ConnectionsController extends \OPNsense\Base\IndexController 34 | { 35 | public function indexAction() 36 | { 37 | $this->view->pick('ThreatPatrols/Autossh/connections'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/opnsense/mvc/app/controllers/ThreatPatrols/Autossh/SettingsController.php: -------------------------------------------------------------------------------- 1 | 5 | Copyright (c) 2018 Verb Networks Pty Ltd 6 | Copyright (c) 2018 Nicholas de Jong 7 | All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without modification, 10 | are permitted provided that the following conditions are met: 11 | 12 | 1. Redistributions of source code must retain the above copyright notice, this 13 | list of conditions and the following disclaimer. 14 | 15 | 2. Redistributions in binary form must reproduce the above copyright notice, 16 | this list of conditions and the following disclaimer in the documentation 17 | and/or other materials provided with the distribution. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 23 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 26 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | namespace ThreatPatrols\Autossh; 32 | 33 | use OPNsense\Core\Backend; 34 | 35 | class SettingsController extends \OPNsense\Base\IndexController 36 | { 37 | public function indexAction() 38 | { 39 | $backend = new Backend(); 40 | $response = json_decode(trim($backend->configdRun("autossh version")), true); 41 | $this->view->autossh_version = $response["version"]; 42 | $this->view->pick('ThreatPatrols/Autossh/settings'); 43 | $this->view->formDialogTunnels = $this->getForm('tunnels'); 44 | $this->view->formDialogKeys = $this->getForm('keys'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/opnsense/mvc/app/controllers/ThreatPatrols/Autossh/forms/keys.xml: -------------------------------------------------------------------------------- 1 |
2 | 3 | key.name 4 | 5 | text 6 | Provide a name for this key. 7 | 8 | 9 | key.type 10 | 11 | dropdown 12 | Select the type of SSH key to be generated - cannot be modified after the key has been generated. 13 | 14 | 15 | key.description 16 | 17 | text 18 | Free form input to describe this SSH key (not parsed). 19 | 20 | 21 | key.key_fingerprint 22 | 23 | info 24 | SSH key fingerprint for reference. 25 | 26 |
27 | -------------------------------------------------------------------------------- /src/opnsense/mvc/app/controllers/ThreatPatrols/Autossh/forms/tunnels.xml: -------------------------------------------------------------------------------- 1 |
2 | 3 | tunnel.enabled 4 | 5 | checkbox 6 | Enable this Autossh tunnel. 7 | 8 | 9 | tunnel.description 10 | 11 | text 12 | Free form text field available to describe this connection if required. 13 | 14 | 15 | 16 | 17 | 18 | tunnel.user 19 | 20 | text 21 | Provide the username for this connection. 22 | 23 | 24 | tunnel.hostname 25 | 26 | text 27 | Provide the hostname or address to use for this connection. 28 | 29 | 30 | tunnel.port 31 | 32 | text 33 | 22 if not provided.]]> 34 | 35 | 36 | tunnel.ssh_key 37 | 38 | dropdown 39 | Select the SSH key by name to use for this connection - this key must be generated prior to creating a ssh-tunnel. 40 | 41 | 42 | tunnel.strict_host_key_checking 43 | 44 | dropdown 45 | StrictHostKeyChecking ssh-client option.]]> 46 | 47 | 48 | tunnel.bind_interface 49 | 50 | dropdown 51 | Select the interface to bind and originate this SSH connection from. 52 | 53 | 54 | tunnel.local_forward 55 | 56 | text 57 | 2222 127.0.0.1:22 would make the remote system TCP22 available at TCP2222 on this system at 127.0.0.1. Leave empty for no local forwarding. Refer to LocalForward ssh-client option.]]> 58 | 59 | 60 | tunnel.remote_forward 61 | 62 | text 63 | *:2222 127.0.0.1:22 would allow network users on the remote site to connect to TCP2222 which forwards to TCP22 on this system at 127.0.0.1. Leave empty for no remote forwarding. Refer to RemoteForward ssh-client option.]]> 64 | 65 | 66 | tunnel.dynamic_forward 67 | 68 | text 69 | *:1080 would allow local network users to use this system as a SOCKS proxy appearing as if at the remote system. Leave empty for no dynamic forwarding. Refer to DynamicForward ssh-client option.]]> 70 | 71 | 72 | tunnel.gateway_ports 73 | 74 | dropdown 75 | GatewayPorts ssh-client option.]]> 76 | 77 | 78 | 79 | 80 | 81 | tunnel.address_family 82 | 83 | dropdown 84 | AddressFamily ssh-client option.]]> 85 | true 86 | 87 | 88 | tunnel.check_host_ip 89 | 90 | dropdown 91 | CheckHostIP ssh-client option.]]> 92 | true 93 | 94 | 95 | tunnel.ciphers 96 | 97 | select_multiple 98 | Ciphers ssh-client option.]]> 99 | true 100 | 101 | 102 | tunnel.compression 103 | 104 | dropdown 105 | Compression ssh-client option.]]> 106 | true 107 | 108 | 109 | tunnel.connection_attempts 110 | 111 | text 112 | ConnectionAttempts ssh-client option.]]> 113 | true 114 | 115 | 116 | tunnel.connect_timeout 117 | 118 | text 119 | ConnectTimeout ssh-client option.]]> 120 | true 121 | 122 | 123 | tunnel.host_key_algorithms 124 | 125 | select_multiple 126 | HostKeyAlgorithms ssh-client option.]]> 127 | true 128 | 129 | 130 | tunnel.kex_algorithms 131 | 132 | select_multiple 133 | KeyExchangeAlgorithms ssh-client option.]]> 134 | true 135 | 136 | 137 | tunnel.log_level 138 | 139 | dropdown 140 | LogLevel ssh-client option.]]> 141 | true 142 | 143 | 144 | tunnel.macs 145 | 146 | select_multiple 147 | MACs ssh-client option.]]> 148 | true 149 | 150 | 151 | tunnel.pubkey_accepted_key_types 152 | 153 | select_multiple 154 | PubkeyAcceptedKeyTypes ssh-client option.]]> 155 | true 156 | 157 | 158 | tunnel.rekey_limit 159 | 160 | dropdown 161 | RekeyLimit ssh-client option.]]> 162 | true 163 | 164 | 165 | tunnel.server_alive_count_max 166 | 167 | text 168 | ServerAliveCountMax ssh-client option.]]> 169 | true 170 | 171 | 172 | tunnel.server_alive_interval 173 | 174 | text 175 | ServerAliveInterval ssh-client option.]]> 176 | true 177 | 178 | 179 | tunnel.tcp_keep_alive 180 | 181 | dropdown 182 | TCPKeepAlive ssh-client option.]]> 183 | true 184 | 185 | 186 | tunnel.update_host_keys 187 | 188 | dropdown 189 | UpdateHostKeys ssh-client option.]]> 190 | true 191 | 192 | 193 | tunnel.verify_host_key_dns 194 | 195 | dropdown 196 | VerifyHostKeyDNS ssh-client option.]]> 197 | true 198 | 199 | 200 |
201 | -------------------------------------------------------------------------------- /src/opnsense/mvc/app/models/ThreatPatrols/Autossh/ACL/ACL.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | VPN: Autossh 4 | 5 | ui/autossh/* 6 | api/autossh/* 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/opnsense/mvc/app/models/ThreatPatrols/Autossh/Autossh.php: -------------------------------------------------------------------------------- 1 | 5 | Copyright (c) 2018 Verb Networks Pty Ltd 6 | Copyright (c) 2018 Nicholas de Jong 7 | All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without modification, 10 | are permitted provided that the following conditions are met: 11 | 12 | 1. Redistributions of source code must retain the above copyright notice, this 13 | list of conditions and the following disclaimer. 14 | 15 | 2. Redistributions in binary form must reproduce the above copyright notice, 16 | this list of conditions and the following disclaimer in the documentation 17 | and/or other materials provided with the distribution. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 23 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 26 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | namespace ThreatPatrols\Autossh; 32 | 33 | use OPNsense\Base\BaseModel; 34 | 35 | class Autossh extends BaseModel 36 | { 37 | } 38 | -------------------------------------------------------------------------------- /src/opnsense/mvc/app/models/ThreatPatrols/Autossh/Autossh.xml: -------------------------------------------------------------------------------- 1 | 2 | //ThreatPatrols/Autossh 3 | Threat Patrols "Autossh" plugin for OPNsense 4 | 0.3.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | Y 12 | /^([0-9a-z\ \.\-\,\_\@\:]){1,128}$/i 13 | Must provide a valid key name value 14 | 15 | 16 | 17 | Y 18 | N 19 | ed25519 20 | 21 | DSA-1024 22 | ECDSA-256 23 | ECDSA-384 24 | ECDSA-521 25 | Ed25519 26 | RSA-1024 27 | RSA-2048 28 | RSA-4096 29 | 30 | 31 | 32 | 33 | N 34 | 35 | 36 | 37 | N 38 | 39 | 40 | 41 | N 42 | 43 | 44 | 45 | N 46 | 47 | 48 | 49 | N 50 | /^([0-9a-z\ \.\-\,\_\@\:]){0,1024}$/i 51 | Invalid description string 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 1 64 | Y 65 | 66 | 67 | 68 | N 69 | /^([0-9a-z\ \.\-\,\_\@\:]){0,1024}$/i 70 | Invalid description string 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | ThreatPatrols.Autossh.Autossh 79 | keys.key 80 | name 81 | 82 | 83 | Related ssh-key not found 84 | Y 85 | 86 | 87 | 88 | 89 | 90 | 91 | Y 92 | N 93 | any 94 | 95 | IPv4 or IPv6 96 | IPv4 only 97 | IPv6 only 98 | 99 | 100 | 101 | 102 | Y 103 | N 104 | 105 | 106 | 107 | Y 108 | N 109 | yes 110 | 111 | Yes 112 | No 113 | 114 | 115 | 116 | 117 | Y 118 | Y 119 | chacha20-poly1305@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com 120 | 121 | chacha20-poly1305@openssh.com 122 | aes128-ctr 123 | aes192-ctr 124 | aes256-ctr 125 | aes128-gcm@openssh.com 126 | aes256-gcm@openssh.com 127 | 128 | 129 | 130 | 131 | Y 132 | N 133 | no 134 | 135 | Yes 136 | No 137 | 138 | 139 | 140 | 141 | 5 142 | Y 143 | 1 144 | 9999 145 | Provide valid integer between 1 and 9999 146 | 147 | 148 | 149 | 15 150 | Y 151 | 1 152 | 3600 153 | Provide valid value between 1 and 3600 154 | 155 | 156 | 157 | N 158 | /^([0-9a-z\ \.\-\_\:\*]){0,256}$/i 159 | Must provide valid SSH client DynamicForward string 160 | 161 | 162 | 163 | Y 164 | N 165 | no 166 | 167 | Yes 168 | No 169 | 170 | 171 | 172 | 173 | Y 174 | Y 175 | ecdsa-sha2-nistp256-cert-v01@openssh.com,ecdsa-sha2-nistp384-cert-v01@openssh.com,ecdsa-sha2-nistp521-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-ed25519,ssh-rsa 176 | 177 | ecdsa-sha2-nistp256-cert-v01@openssh.com 178 | ecdsa-sha2-nistp384-cert-v01@openssh.com 179 | ecdsa-sha2-nistp521-cert-v01@openssh.com 180 | ssh-ed25519-cert-v01@openssh.com 181 | ssh-rsa-cert-v01@openssh.com 182 | ecdsa-sha2-nistp256 183 | ecdsa-sha2-nistp384 184 | ecdsa-sha2-nistp521 185 | ssh-ed25519 186 | ssh-rsa 187 | 188 | 189 | 190 | 191 | Y 192 | /^([0-9a-z\.\-\:]){1,512}$/i 193 | Provide a valid hostname or host-address string 194 | 195 | 196 | 197 | Y 198 | Y 199 | curve25519-sha256,curve25519-sha256,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha256,diffie-hellman-group14-sha1 200 | 201 | curve25519-sha256 202 | curve25519-sha256@libssh.org 203 | ecdh-sha2-nistp256 204 | ecdh-sha2-nistp384 205 | ecdh-sha2-nistp521 206 | diffie-hellman-group-exchange-sha256 207 | diffie-hellman-group16-sha512 208 | diffie-hellman-group18-sha512 209 | diffie-hellman-group-exchange-sha1 210 | diffie-hellman-group14-sha256 211 | diffie-hellman-group14-sha1 212 | 213 | 214 | 215 | 216 | N 217 | /^([0-9a-z\ \.\-\_\:\*]){0,256}$/i 218 | Must provide valid SSH client LocalForward string 219 | 220 | 221 | 222 | Y 223 | N 224 | INFO 225 | 226 | Quiet 227 | Fatal 228 | Error 229 | Info 230 | Verbose 231 | Debug Level 1 232 | Debug Level 2 233 | Debug Level 3 234 | 235 | 236 | 237 | 238 | Y 239 | Y 240 | umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1 241 | 242 | umac-64-etm@openssh.com 243 | umac-128-etm@openssh.com 244 | hmac-sha2-256-etm@openssh.com 245 | hmac-sha2-512-etm@openssh.com 246 | hmac-sha1-etm@openssh.com 247 | umac-64@openssh.com 248 | umac-128@openssh.com 249 | hmac-sha2-256 250 | hmac-sha2-512 251 | hmac-sha1 252 | 253 | 254 | 255 | 256 | N 257 | Provide valid TCP port number 258 | 259 | 260 | 261 | Y 262 | Y 263 | ecdsa-sha2-nistp256-cert-v01@openssh.com,ecdsa-sha2-nistp384-cert-v01@openssh.com,ecdsa-sha2-nistp521-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-ed25519,ssh-rsa 264 | 265 | ecdsa-sha2-nistp256-cert-v01@openssh.com 266 | ecdsa-sha2-nistp384-cert-v01@openssh.com 267 | ecdsa-sha2-nistp521-cert-v01@openssh.com 268 | ssh-ed25519-cert-v01@openssh.com 269 | ssh-rsa-cert-v01@openssh.com 270 | ecdsa-sha2-nistp256 271 | ecdsa-sha2-nistp384 272 | ecdsa-sha2-nistp521 273 | ssh-ed25519 274 | ssh-rsa 275 | 276 | 277 | 278 | 279 | Y 280 | N 281 | default none 282 | 283 | default none 284 | 1G 1h 285 | 1G 4h 286 | 1G 8h 287 | 2G 1h 288 | 2G 4h 289 | 2G 8h 290 | 4G 1h 291 | 4G 4h 292 | 4G 8h 293 | 294 | 295 | 296 | 297 | N 298 | /^([0-9a-z\ \.\-\_\:\*]){0,256}$/i 299 | Must provide valid SSH client RemoteForward string 300 | 301 | 302 | 303 | 3 304 | Y 305 | 1 306 | 9999 307 | Provide valid integer between 1 and 9999 308 | 309 | 310 | 311 | 30 312 | Y 313 | 0 314 | 3600 315 | Provide valid integer between 0 and 3600 316 | 317 | 318 | 319 | Y 320 | N 321 | accept-new 322 | 323 | Accept New - allow first new key 324 | Yes - deny host key changes 325 | No - allow any host key 326 | 327 | 328 | 329 | 330 | Y 331 | N 332 | yes 333 | 334 | Yes 335 | No 336 | 337 | 338 | 339 | 340 | Y 341 | N 342 | no 343 | 344 | No 345 | Layer 2 - Ethernet 346 | Layer 3 - Point to Point 347 | 348 | 349 | 350 | 351 | N 352 | /^([0-9any\:]){0,16}$/i 353 | Provide a valid tunnel device value 354 | 355 | 356 | 357 | Y 358 | N 359 | yes 360 | 361 | Yes 362 | No 363 | 364 | 365 | 366 | 367 | Y 368 | /^([0-9a-z\.\-\_]){1,128}$/i 369 | Provide a valid user string 370 | 371 | 372 | 373 | Y 374 | N 375 | no 376 | 377 | Yes 378 | No 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | -------------------------------------------------------------------------------- /src/opnsense/mvc/app/models/ThreatPatrols/Autossh/Menu/Menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/opnsense/mvc/app/models/ThreatPatrols/Autossh/Migrations/M0_3_0.php: -------------------------------------------------------------------------------- 1 | 4 | Copyright (c) 2018 Verb Networks Pty Ltd 5 | Copyright (c) 2018 Nicholas de Jong 6 | All rights reserved. 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 21 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | namespace ThreatPatrols\Autossh\Migrations; 27 | 28 | use OPNsense\Base\FieldTypes\BaseField; 29 | use OPNsense\Core\Config; 30 | use OPNsense\Base\BaseModelMigration; 31 | use ThreatPatrols\Autossh\Autossh; 32 | 33 | class M0_3_0 extends BaseModelMigration 34 | { 35 | /** 36 | * Migrate incorrectly stored settings 37 | * @param AutoSSH $model 38 | */ 39 | public function post($model) 40 | { 41 | $sshCiphers = array( 42 | "ciphers_01" => "chacha20-poly1305@openssh.com", 43 | "ciphers_02" => "aes128-ctr", 44 | "ciphers_03" => "aes192-ctr", 45 | "ciphers_04" => "aes256-ctr", 46 | "ciphers_05" => "aes128-gcm@openssh.com", 47 | "ciphers_06" => "aes256-gcm@openssh.com" 48 | ); 49 | $sshHostKex = array( 50 | "host_key_algorithms_01" => "ecdsa-sha2-nistp256-cert-v01@openssh.com", 51 | "host_key_algorithms_02" => "ecdsa-sha2-nistp384-cert-v01@openssh.com", 52 | "host_key_algorithms_03" => "ecdsa-sha2-nistp521-cert-v01@openssh.com", 53 | "host_key_algorithms_04" => "ssh-ed25519-cert-v01@openssh.com", 54 | "host_key_algorithms_05" => "ssh-rsa-cert-v01@openssh.com", 55 | "host_key_algorithms_06" => "ecdsa-sha2-nistp256", 56 | "host_key_algorithms_07" => "ecdsa-sha2-nistp384", 57 | "host_key_algorithms_08" => "ecdsa-sha2-nistp521", 58 | "host_key_algorithms_09" => "ssh-ed25519", 59 | "host_key_algorithms_10" => "ssh-rsa" 60 | ); 61 | $sshKex = array( 62 | "kex_algorithms_01" => "curve25519-sha256", 63 | "kex_algorithms_02" => "curve25519-sha256", 64 | "kex_algorithms_03" => "ecdh-sha2-nistp256", 65 | "kex_algorithms_04" => "ecdh-sha2-nistp384", 66 | "kex_algorithms_05" => "ecdh-sha2-nistp521", 67 | "kex_algorithms_06" => "diffie-hellman-group-exchange-sha256", 68 | "kex_algorithms_07" => "diffie-hellman-group16-sha512", 69 | "kex_algorithms_08" => "diffie-hellman-group18-sha512", 70 | "kex_algorithms_09" => "diffie-hellman-group-exchange-sha1", 71 | "kex_algorithms_10" => "diffie-hellman-group14-sha256", 72 | "kex_algorithms_11" => "diffie-hellman-group14-sha1" 73 | ); 74 | $sshMacs = array( 75 | "macs_01" => "umac-64-etm@openssh.com", 76 | "macs_02" => "umac-128-etm@openssh.com", 77 | "macs_03" => "hmac-sha2-256-etm@openssh.com", 78 | "macs_04" => "hmac-sha2-512-etm@openssh.com", 79 | "macs_05" => "hmac-sha1-etm@openssh.com", 80 | "macs_06" => "umac-64@openssh.com", 81 | "macs_07" => "umac-128@openssh.com", 82 | "macs_08" => "hmac-sha2-256", 83 | "macs_09" => "hmac-sha2-512", 84 | "macs_10" => "hmac-sha1" 85 | ); 86 | $sshKeyTypes = array( 87 | "pubkey_accepted_key_types_01" => "ecdsa-sha2-nistp256-cert-v01@openssh.com", 88 | "pubkey_accepted_key_types_02" => "ecdsa-sha2-nistp384-cert-v01@openssh.com", 89 | "pubkey_accepted_key_types_03" => "ecdsa-sha2-nistp521-cert-v01@openssh.com", 90 | "pubkey_accepted_key_types_04" => "ssh-ed25519-cert-v01@openssh.com", 91 | "pubkey_accepted_key_types_05" => "ssh-rsa-cert-v01@openssh.com", 92 | "pubkey_accepted_key_types_06" => "ecdsa-sha2-nistp256", 93 | "pubkey_accepted_key_types_07" => "ecdsa-sha2-nistp384", 94 | "pubkey_accepted_key_types_08" => "ecdsa-sha2-nistp521", 95 | "pubkey_accepted_key_types_09" => "ssh-ed25519", 96 | "pubkey_accepted_key_types_10" => "ssh-rsa" 97 | ); 98 | $sshRekeyLimits = array( 99 | "rekey_limit_01" => "default none", 100 | "rekey_limit_02" => "1G 1h", 101 | "rekey_limit_03" => "1G 4h", 102 | "rekey_limit_04" => "1G 8h", 103 | "rekey_limit_05" => "2G 1h", 104 | "rekey_limit_06" => "2G 4h", 105 | "rekey_limit_07" => "2G 8h", 106 | "rekey_limit_08" => "4G 1h", 107 | "rekey_limit_09" => "4G 4h", 108 | "rekey_limit_10" => "4G 8h" 109 | ); 110 | $cfgObj = Config::getInstance()->object(); 111 | if (!isset($cfgObj->ThreatPatrols->Autossh->tunnels->tunnel)) { 112 | return; 113 | } 114 | foreach ($cfgObj->ThreatPatrols->Autossh->tunnels->tunnel as $tunnel) { 115 | foreach (explode(',',$tunnel->ciphers) as $cipher) { 116 | $newCiphers[] = $sshCiphers[$cipher]; 117 | } 118 | $tunnel->ciphers = implode(',',$newCiphers); 119 | foreach (explode(',',$tunnel->host_key_algorithms) as $hostkex) { 120 | $newHostKex[] = $sshHostKex[$hostkex]; 121 | } 122 | $tunnel->host_key_algorithms = implode(',',$newHostKex); 123 | foreach (explode(',',$tunnel->kex_algorithms) as $kex) { 124 | $newKex[] = $sshKex[$kex]; 125 | } 126 | $tunnel->kex_algorithms = implode(',',$newKex); 127 | foreach (explode(',',$tunnel->macs) as $mac) { 128 | $newMacs[] = $sshMacs[$mac]; 129 | } 130 | $tunnel->macs = implode(',',$newMacs); 131 | foreach (explode(',',$tunnel->pubkey_accepted_key_types) as $pubkeytype) { 132 | $newKeyTypes[] = $sshKeyTypes[$pubkeytype]; 133 | } 134 | $tunnel->pubkey_accepted_key_types = implode(',',$newKeyTypes); 135 | $rekeyLimit = (string)$tunnel->rekey_limit; 136 | $tunnel->rekey_limit = $sshRekeyLimits[$rekeyLimit]; 137 | } 138 | // perform validation on the data in our model 139 | $validationMessages = $model->performValidation(); 140 | foreach ($validationMessages as $messsage) { 141 | echo "validation failure on field ". $messsage->getField()." returning message : ". $messsage->getMessage()."\n"; 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/opnsense/mvc/app/views/ThreatPatrols/Autossh/connections.volt: -------------------------------------------------------------------------------- 1 | {# 2 | Copyright (c) 2022 Threat Patrols Pty Ltd 3 | Copyright (c) 2018 Verb Networks Pty Ltd 4 | Copyright (c) 2018 Nicholas de Jong 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | 2. Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 21 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | #} 28 | 29 | 30 | 31 | 32 |
33 |
34 |
35 | 36 |
37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
{{ lang._('Id') }}{{ lang._('Connection') }}{{ lang._('Interface') }}{{ lang._('SSH Key') }}{{ lang._('Forwards') }}{{ lang._('Status') }}{{ lang._('Actions') }}
53 |
54 |
55 |
56 | 57 |
58 |
59 |
60 | 61 | {{ partial("layout_partials/base_dialog_processing") }} 62 | 63 | 68 | 69 | 181 | -------------------------------------------------------------------------------- /src/opnsense/mvc/app/views/ThreatPatrols/Autossh/settings.volt: -------------------------------------------------------------------------------- 1 | {# 2 | Copyright (c) 2022 Threat Patrols Pty Ltd 3 | Copyright (c) 2018 Verb Networks Pty Ltd 4 | Copyright (c) 2018 Nicholas de Jong 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | 2. Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 21 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | #} 28 | 29 | 30 | 34 | 35 | 40 | 41 |
42 | 43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 68 | 69 | 70 | 71 |
{{ lang._('Enabled') }}{{ lang._('Id') }}{{ lang._('Description') }}{{ lang._('Connection') }}{{ lang._('Local Forward') }}{{ lang._('Dynamic Forward') }}{{ lang._('Remote Forward') }}{{ lang._('Commands') }}
65 | 66 | 67 |
72 |
73 | 74 |
75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 98 | 99 | 100 | 101 |
{{ lang._('Id') }}{{ lang._('Name') }}{{ lang._('Description') }}{{ lang._('SSH Key Fingerprint') }}{{ lang._('Type') }}{{ lang._('Created') }}{{ lang._('Commands') }}
95 | 96 | 97 |
102 |
103 | 104 |
105 |
106 | 107 |
108 |

Autossh

109 |

110 | The Autossh plugin for OPNsense is a wrapper for the autossh system-package that 111 | allows for establishing persistent reliable SSH tunnels with remote hosts. 112 | It can be used to solve a wide range of connection challenges through the (sometimes 113 | creative) use of TCP port-forwards. 114 |

115 | 116 |

117 | Autossh tunnels can be used to quickly solve a wide range of networking challenges without the need for additional VPN servers and clients: 118 |

119 |
    120 |
  • Provide reverse-remote access to a site that has no public addresses, such as when ISPs use NAT.
  • 121 |
  • Ensure redundant multipath reverse-remote access via both primary and secondary connections via interface binding.
  • 122 |
  • Create your own "privacy" VPN system for local network users using a SOCKS proxy (ssh-dynamic-forward) to a remote system.
  • 123 |
  • Provide local network access to remote system services such as SMTP relays or another remote TCP services.
  • 124 |
  • Provide reverse-remote access to local network services such local RDP services.
  • 125 |
126 | 127 |

Version

128 |
    129 |
  • {{ autossh_version }}
  • 130 |
131 | 132 |
133 | 134 |

Documentation

135 | 138 | 139 |

Issues

140 | 143 | 144 |

Source

145 | 148 | 149 |

Copyright

150 |

151 | Autossh (c) 2022 Threat Patrols Pty Ltd 152 |

153 |

154 | All rights reserved. 155 |

156 | 157 |

License

158 |

BSD-2-Clause - see source LICENSE file for details.

159 | 160 |
161 | 162 |
163 |
164 |
165 | 166 | {# include dialogs #} 167 | {{ partial('layout_partials/base_dialog',['fields':formDialogKeys,'id':'DialogKeys','label':lang._('SSH key')])}} 168 | {{ partial('layout_partials/base_dialog',['fields':formDialogTunnels,'id':'DialogTunnels','label':lang._('SSH tunnel')])}} 169 | 170 | 179 | 180 | 283 | -------------------------------------------------------------------------------- /src/opnsense/scripts/ThreatPatrols/Autossh/.pylintrc: -------------------------------------------------------------------------------- 1 | 2 | [MASTER] 3 | disable = 4 | invalid-name, 5 | missing-class-docstring, 6 | missing-function-docstring, 7 | missing-module-docstring, 8 | too-few-public-methods, 9 | logging-fstring-interpolation, 10 | no-else-continue, 11 | no-else-return, 12 | 13 | indent-string=' ' 14 | max-line-length = 120 15 | -------------------------------------------------------------------------------- /src/opnsense/scripts/ThreatPatrols/Autossh/autossh.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright (c) 2022 Threat Patrols Pty Ltd 6 | Copyright (c) 2018 Verb Networks Pty Ltd 7 | Copyright (c) 2018 Nicholas de Jong 8 | All rights reserved. 9 | 10 | Redistribution and use in source and binary forms, with or without modification, 11 | are permitted provided that the following conditions are met: 12 | 13 | 1. Redistributions of source code must retain the above copyright notice, this 14 | list of conditions and the following disclaimer. 15 | 16 | 2. Redistributions in binary form must reproduce the above copyright notice, 17 | this list of conditions and the following disclaimer in the documentation 18 | and/or other materials provided with the distribution. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 24 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 27 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | """ 31 | 32 | import sys 33 | import json 34 | import logging 35 | import argparse 36 | from signal import signal, SIGINT 37 | 38 | from autossh.utils.logger_helpers import init_logger 39 | from autossh.utils.key_gen import key_gen as autossh_key_gen 40 | from autossh.utils.config_helper import AutosshConfigHelper 41 | from autossh.utils.host_keys import host_keys as autossh_host_keys 42 | from autossh.utils.connection_status import connection_status as autossh_connection_status 43 | from autossh.exceptions import AutosshException 44 | 45 | from autossh.__version__ import __version__ 46 | 47 | from autossh.vars import __title__ 48 | from autossh.vars import __logging_console_level__ 49 | from autossh.vars import __logging_syslog_level__ 50 | from autossh.vars import __system_config_file__ 51 | from autossh.vars import __autossh_config_file__ 52 | from autossh.vars import __autossh_data_model_file__ 53 | 54 | 55 | logger = logging.getLogger(__title__) 56 | 57 | 58 | def sigint_handler(__signal_received, __frame): 59 | print("SIGINT received, exiting.") 60 | sys.exit(1) 61 | 62 | 63 | def autossh_cli(): 64 | 65 | # args 66 | args = __argparse() 67 | 68 | # action - version 69 | if args.action == "version": 70 | return {"version": __version__} 71 | 72 | if args.action == "key_gen": 73 | return autossh_key_gen(key_type=args.key_type) 74 | 75 | elif args.action == "config_helper": 76 | return AutosshConfigHelper( 77 | system_config_filename=args.system_config, 78 | ssh_config_filename=args.ssh_config, 79 | data_model_filename=args.data_model, 80 | ).process() 81 | 82 | elif args.action == "host_keys" and args.connection_uuid: 83 | return autossh_host_keys(connection_uuid=args.connection_uuid) 84 | 85 | elif args.action == "connection_status" and args.connection_uuid: 86 | return autossh_connection_status(connection_uuid=args.connection_uuid, ssh_config_file=args.ssh_config) 87 | 88 | raise AutosshException("Unknown or incomplete arguments") 89 | 90 | 91 | def __argparse(): 92 | 93 | parser = argparse.ArgumentParser(add_help=True, description="Autossh tunnel management interface") 94 | 95 | parser.add_argument("--debug", action="store_true", help="Set logging to DEBUG level") 96 | 97 | parser.add_argument( 98 | "action", 99 | type=str, 100 | metavar="", 101 | choices=["version", "key_gen", "config_helper", "host_keys", "connection_status"], 102 | help="Autossh action request", 103 | ) 104 | 105 | # Actions: key_gen 106 | parser.add_argument("--key_type", type=str, help="Type of SSH key to generate") 107 | 108 | # Actions: host_keys, connection_status 109 | parser.add_argument("--connection_uuid", type=str, help="UUID of the Autossh tunnel connection to use") 110 | 111 | # Actions: config_helper 112 | parser.add_argument( 113 | "--ssh_config", 114 | default=__autossh_config_file__, 115 | type=str, 116 | help="Overwrites the default ssh_config file (autossh.conf) file location", 117 | ) 118 | parser.add_argument( 119 | "--system_config", 120 | default=__system_config_file__, 121 | type=str, 122 | help="Overwrites the default system config file (config.xml) file location", 123 | ) 124 | parser.add_argument( 125 | "--data_model", 126 | default=__autossh_data_model_file__, 127 | type=str, 128 | help="Overwrites the default autossh data model file (Autossh.xml) file location", 129 | ) 130 | 131 | parsed_args = parser.parse_args() 132 | 133 | for arg_name in vars(parsed_args): 134 | value = getattr(parsed_args, arg_name) 135 | if len(str(value)) == 0: 136 | setattr(parsed_args, arg_name, None) 137 | 138 | return parsed_args 139 | 140 | 141 | if __name__ == "__main__": 142 | signal(SIGINT, sigint_handler) 143 | 144 | if "--debug" in sys.argv: 145 | debug = True 146 | init_logger(console_level="debug", syslog_level="debug") 147 | else: 148 | debug = False 149 | init_logger(console_level=__logging_console_level__, syslog_level=__logging_syslog_level__) 150 | 151 | try: 152 | response = autossh_cli() 153 | 154 | except AutosshException as e: 155 | message = str(e).strip() 156 | logger.error(message) 157 | response = { 158 | "status": "fail", 159 | "message": message, 160 | } 161 | 162 | except Exception as e: # noqa pylint:disable=broad-except 163 | message = str(e).strip() 164 | logger.critical(msg=message, exc_info=debug) # provides stacktrace if debug mode 165 | response = { 166 | "status": "fail", 167 | "message": message, 168 | } 169 | 170 | print(json.dumps(response, default=str, sort_keys=True, indent=" ")) 171 | -------------------------------------------------------------------------------- /src/opnsense/scripts/ThreatPatrols/Autossh/autossh/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/threatpatrols/opnsense-plugin-autossh/b974c74b377da626ad8d760f28c1c3644e817703/src/opnsense/scripts/ThreatPatrols/Autossh/autossh/__init__.py -------------------------------------------------------------------------------- /src/opnsense/scripts/ThreatPatrols/Autossh/autossh/__version__.py: -------------------------------------------------------------------------------- 1 | __version__ = "devel" 2 | -------------------------------------------------------------------------------- /src/opnsense/scripts/ThreatPatrols/Autossh/autossh/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2022 Threat Patrols Pty Ltd 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | """ 26 | 27 | 28 | class AutosshException(Exception): 29 | def __init__(self, *args): 30 | args_decoded = [] 31 | for _, arg_value in enumerate(args): 32 | if isinstance(arg_value, bytes): 33 | args_decoded.append(arg_value.decode("utf8")) 34 | else: 35 | args_decoded.append(arg_value) 36 | super().__init__(*args_decoded) 37 | -------------------------------------------------------------------------------- /src/opnsense/scripts/ThreatPatrols/Autossh/autossh/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/threatpatrols/opnsense-plugin-autossh/b974c74b377da626ad8d760f28c1c3644e817703/src/opnsense/scripts/ThreatPatrols/Autossh/autossh/utils/__init__.py -------------------------------------------------------------------------------- /src/opnsense/scripts/ThreatPatrols/Autossh/autossh/utils/config_helper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright (c) 2022 Threat Patrols Pty Ltd 6 | Copyright (c) 2018 Verb Networks Pty Ltd 7 | Copyright (c) 2018 Nicholas de Jong 8 | All rights reserved. 9 | 10 | Redistribution and use in source and binary forms, with or without modification, 11 | are permitted provided that the following conditions are met: 12 | 13 | 1. Redistributions of source code must retain the above copyright notice, this 14 | list of conditions and the following disclaimer. 15 | 16 | 2. Redistributions in binary form must reproduce the above copyright notice, 17 | this list of conditions and the following disclaimer in the documentation 18 | and/or other materials provided with the distribution. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 24 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 27 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | """ 31 | 32 | import os 33 | import io 34 | import sys 35 | import base64 36 | import logging 37 | import configparser 38 | 39 | from autossh.exceptions import AutosshException 40 | from autossh.vars import __title__ 41 | 42 | sys.path.insert(0, "/usr/local/opnsense/service/modules") 43 | from config import Config as OpnsenseConfig # OPNsense Config class 44 | 45 | 46 | logger = logging.getLogger(__title__) 47 | 48 | 49 | class AutosshConfigHelper: 50 | """ 51 | AutosshConfigHelper provides autossh configuration file helper processing 52 | """ 53 | 54 | ssh_config_filename = None 55 | 56 | __ssh_config = None 57 | __system_config = None 58 | __data_model = None 59 | 60 | def __init__(self, system_config_filename, ssh_config_filename, data_model_filename): 61 | self.ssh_config_filename = ssh_config_filename 62 | self.__system_config = self.__load_system_config(system_config_filename) 63 | self.__ssh_config = self.__load_ssh_config(self.ssh_config_filename) 64 | self.__data_model = self.__load_data_model(data_model_filename) 65 | 66 | def process(self): 67 | logger.debug(f"process()") 68 | 69 | host_section_count = 0 70 | for host_section in self.get_ssh_config_host_sections(): 71 | self.do_write_identity_file(host_section) 72 | self.do_touch_known_hosts(host_section) 73 | self.do_replace_bind_interface(host_section) 74 | self.do_replace_by_data_model(host_section, "Ciphers", "items.tunnels.tunnel.ciphers.optionvalues") 75 | self.do_replace_by_data_model( 76 | host_section, "HostKeyAlgorithms", "items.tunnels.tunnel.host_key_algorithms.optionvalues" 77 | ) 78 | self.do_replace_by_data_model( 79 | host_section, "KexAlgorithms", "items.tunnels.tunnel.kex_algorithms.optionvalues" 80 | ) 81 | self.do_replace_by_data_model(host_section, "MACs", "items.tunnels.tunnel.macs.optionvalues") 82 | self.do_replace_by_data_model( 83 | host_section, "PubkeyAcceptedKeyTypes", "items.tunnels.tunnel.pubkey_accepted_key_types.optionvalues" 84 | ) 85 | self.do_replace_by_data_model(host_section, "RekeyLimit", "items.tunnels.tunnel.rekey_limit.optionvalues") 86 | host_section_count += 1 87 | 88 | return {"status": "success", "message": f"Updated {host_section_count} configuration host sections"} 89 | 90 | def get_ssh_config_host_sections(self) -> list: 91 | logger.debug(f"get_ssh_config_host_sections()") 92 | return list(self.__ssh_config.keys()) 93 | 94 | def do_write_identity_file(self, host_section) -> True: 95 | logger.debug(f"do_write_identity_file(host_section={host_section})") 96 | 97 | ssh_config_keypath = "{}.IdentityFile".format(host_section) 98 | identity_file = self.__get_by_keypath(ssh_config_keypath, self.__ssh_config) 99 | if identity_file is None: 100 | raise AutosshException("Could not locate required ssh_config option", ssh_config_keypath) 101 | 102 | ssh_key_uuid_keypath = "__uuid__.{}.ssh_key".format(host_section) 103 | ssh_key_uuid = self.__get_by_keypath(ssh_key_uuid_keypath, self.__system_config) 104 | if not ssh_key_uuid: 105 | raise AutosshException("Could not locate required system_config option", ssh_key_uuid_keypath) 106 | 107 | ssh_key_private_keypath = "__uuid__.{}.key_private".format(ssh_key_uuid) 108 | ssh_key_private_b64 = self.__get_by_keypath(ssh_key_private_keypath, self.__system_config) 109 | if not ssh_key_private_b64: 110 | raise AutosshException("Could not locate required system_config option", ssh_key_private_keypath) 111 | 112 | if not os.path.isdir(os.path.dirname(identity_file)): 113 | os.makedirs(os.path.dirname(identity_file), 0o700) 114 | 115 | with os.fdopen(os.open(identity_file, os.O_CREAT | os.O_WRONLY, 0o600), "w") as f: 116 | f.write(base64.b64decode(ssh_key_private_b64).decode("utf8")) 117 | 118 | return True 119 | 120 | def do_touch_known_hosts(self, host_section): 121 | logger.debug(f"do_touch_known_hosts(host_section={host_section})") 122 | 123 | ssh_config_keypath = "{}.UserKnownHostsFile".format(host_section) 124 | known_hosts_file = self.__get_by_keypath(ssh_config_keypath, self.__ssh_config) 125 | if known_hosts_file is None: 126 | raise AutosshException("Could not locate required ssh_config option", ssh_config_keypath) 127 | 128 | if not os.path.isdir(os.path.dirname(known_hosts_file)): 129 | os.makedirs(os.path.dirname(known_hosts_file), 0o700) 130 | 131 | with os.fdopen(os.open(known_hosts_file, os.O_CREAT | os.O_APPEND, 0o600)): 132 | os.utime(known_hosts_file, None) 133 | 134 | return True 135 | 136 | def do_replace_bind_interface(self, host_section) -> bool: 137 | logger.debug(f"do_replace_bind_interface(host_section={host_section})") 138 | 139 | ssh_config_keypath = "{}.BindInterface".format(host_section) 140 | interface_by_name = self.__get_by_keypath(ssh_config_keypath, self.__ssh_config) 141 | if interface_by_name is None: 142 | raise AutosshException("Could not locate required ssh_config section and option", ssh_config_keypath) 143 | 144 | system_config_keypath = "interfaces.{}.if".format(interface_by_name) 145 | interface_by_config = self.__get_by_keypath(system_config_keypath, self.__system_config) 146 | if not interface_by_config: 147 | return False # can occur in edge-case when re-run and this replacement has already been performed 148 | 149 | self.__replace_ssh_config_item(ssh_config_keypath, str(interface_by_config)) 150 | return True 151 | 152 | def do_replace_by_data_model(self, host_section, config_item, keypath) -> True: 153 | logger.debug( 154 | f"do_replace_by_data_model(host_section={host_section}, config_item={config_item}, keypath={keypath})" 155 | ) 156 | 157 | ssh_config_keypath = "{}.{}".format(host_section, config_item) 158 | ssh_config_item = self.__get_by_keypath(ssh_config_keypath, self.__ssh_config) 159 | if not ssh_config_item: 160 | raise AutosshException("Could not locate required ssh_config option", ssh_config_keypath) 161 | 162 | data_model_items = self.__get_by_keypath(keypath, self.__data_model) 163 | if not data_model_items: 164 | raise AutosshException("Could not locate required data_model option", data_model_items) 165 | 166 | for data_model_item_k in data_model_items: 167 | ssh_config_item = ssh_config_item.replace(data_model_item_k, data_model_items[data_model_item_k]) 168 | 169 | self.__replace_ssh_config_item(ssh_config_keypath, ssh_config_item) 170 | return True 171 | 172 | def __replace_ssh_config_item(self, keypath, replacement_string) -> True: 173 | host_uuid, ssh_option = keypath.split(".") 174 | current_string = self.__get_by_keypath(keypath, self.__ssh_config) 175 | 176 | with open(self.ssh_config_filename, "r") as f: 177 | ssh_config_lines = f.readlines() 178 | 179 | in_section = False 180 | ssh_config_content = "" 181 | for ssh_config_line in ssh_config_lines: 182 | if ssh_config_line.startswith("Host "): 183 | if host_uuid in ssh_config_line: 184 | in_section = True 185 | else: 186 | in_section = False 187 | if in_section is True and ssh_option in ssh_config_line and current_string in ssh_config_line: 188 | ssh_config_line = ssh_config_line.replace(current_string, replacement_string) 189 | ssh_config_content += ssh_config_line 190 | 191 | with open(self.ssh_config_filename, "w") as f: 192 | f.write(ssh_config_content) 193 | 194 | return True 195 | 196 | def __get_by_keypath(self, keypath, data): 197 | for item in keypath.split("."): 198 | if item in data: 199 | data = data[item] 200 | else: 201 | return None 202 | return data 203 | 204 | def __load_ssh_config(self, ssh_config_file) -> dict: 205 | if not os.path.isfile(ssh_config_file): 206 | raise AutosshException("Could not find ssh_config_file", ssh_config_file) 207 | with open(ssh_config_file, "r") as f: 208 | ssh_config_raw_lines = f.readlines() 209 | 210 | # Munge ssh_config_raw_lines into a format compatible with Python configparser 211 | ssh_config_raw = "" 212 | for ssh_config_raw_line in ssh_config_raw_lines: 213 | ssh_config_raw_line = ssh_config_raw_line.lstrip(" ") 214 | if ssh_config_raw_line.startswith("Host "): 215 | parts = ssh_config_raw_line.split(" ") 216 | ssh_config_raw += "[{}]\n".format(parts[1].strip()) 217 | elif not ssh_config_raw_line.startswith("#") and len(ssh_config_raw_line.strip()) > 0: 218 | parts = ssh_config_raw_line.split(" ") 219 | ssh_config_raw += parts.pop(0) + ": " + " ".join(parts) 220 | else: 221 | ssh_config_raw += ssh_config_raw_line 222 | 223 | cp = configparser.ConfigParser(allow_no_value=True, interpolation=None) 224 | cp.optionxform = str # preserve upper/lower case 225 | cp.read_file(io.StringIO(ssh_config_raw)) 226 | 227 | ssh_config = {} 228 | for section in cp.sections(): 229 | for option in cp.options(section): 230 | if section not in ssh_config: 231 | ssh_config[section] = {} 232 | ssh_config[section][option] = cp.get(section, option) 233 | 234 | return ssh_config 235 | 236 | def __load_system_config(self, system_config_file) -> dict: 237 | if not os.path.isfile(system_config_file): 238 | raise AutosshException("Could not find system_config_file", system_config_file) 239 | return OpnsenseConfig(system_config_file).get() 240 | 241 | def __load_data_model(self, data_model_file) -> dict: 242 | if not os.path.isfile(data_model_file): 243 | raise AutosshException("Could not find data_model_file", data_model_file) 244 | return OpnsenseConfig(data_model_file).get() 245 | -------------------------------------------------------------------------------- /src/opnsense/scripts/ThreatPatrols/Autossh/autossh/utils/connection_status.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2022 Threat Patrols Pty Ltd 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | """ 26 | 27 | import os 28 | import re 29 | import time 30 | import logging 31 | 32 | from autossh.vars import __title__ 33 | from autossh.vars import __autossh_logfile__ 34 | 35 | from autossh.exceptions import AutosshException 36 | from .exec_helpers import get_pid_command 37 | from .exec_helpers import exec_command 38 | 39 | logger = logging.getLogger(__title__) 40 | 41 | 42 | def connection_status(connection_uuid, ssh_config_file, filemask="/var/run/autossh.{}.{}"): 43 | logger.debug( 44 | f"connection_status(connection_uuid={connection_uuid}, ssh_config_file={ssh_config_file}, filemask={filemask})" 45 | ) 46 | 47 | if not connection_uuid: 48 | raise AutosshException("Must provide connection_uuid value") 49 | 50 | status = { 51 | "enabled": False, 52 | "pids": {"daemon": None, "autossh": None, "ssh": None}, 53 | "starts": None, 54 | "uptime": None, 55 | "last_healthy": None, 56 | "tunnel_device": None, 57 | } 58 | 59 | pid_file = filemask.format(connection_uuid, "pid") 60 | info_file = filemask.format(connection_uuid, "info") 61 | 62 | # enabled 63 | if os.path.isfile(ssh_config_file): 64 | with open(ssh_config_file, "r") as f: 65 | for line in f.readlines(): 66 | if line.startswith("Host ") and connection_uuid in line: 67 | status["enabled"] = True 68 | break 69 | 70 | # daemon - pid 71 | daemon_command = None 72 | if os.path.isfile(pid_file): 73 | with open(pid_file, "r") as f: 74 | pid = int(f.read()) 75 | daemon_command = get_pid_command(pid) 76 | if daemon_command: 77 | status["pids"]["daemon"] = pid 78 | 79 | # autossh - pid 80 | autossh_command = None 81 | if daemon_command: 82 | result = re.search("\[(\d+)\]", daemon_command) 83 | if result: 84 | pid = int(result.group(1)) 85 | autossh_command = get_pid_command(pid) 86 | if autossh_command: 87 | status["pids"]["autossh"] = pid 88 | 89 | # ssh - starts + pid 90 | if autossh_command: 91 | result = re.search("parent of (\d+) \((\d+)\) ", autossh_command) 92 | if result: 93 | pid = int(result.group(1)) 94 | status["starts"] = int(result.group(2)) 95 | ssh_command = get_pid_command(pid) 96 | if ssh_command: 97 | status["pids"]["ssh"] = pid 98 | 99 | # uptime & tunnel_device 100 | if os.path.isfile(info_file): 101 | with open(info_file, "r") as f: 102 | for info_line in f.readlines(): 103 | if info_line.startswith("timestamp"): 104 | parts = info_line.strip().split(" ") 105 | status["uptime"] = int(time.time() - int(parts[1])) 106 | if info_line.startswith("tunnel_device") and "NONE" not in info_line: 107 | parts = info_line.strip().split(" ") 108 | status["tunnel_device"] = parts[1] 109 | 110 | if status["pids"]["autossh"]: 111 | command_line = ( 112 | f'grep "autossh {status["pids"]["autossh"]}" "{__autossh_logfile__}" | grep "autosshd" | ' 113 | f'grep "connection ok" | tail -n1 | cut -d" " -f2' 114 | ) 115 | stdout, stderr, rc = exec_command(command_line) 116 | if stdout and len(stdout) > 16: 117 | status["last_healthy"] = stdout.decode("utf8").strip() 118 | 119 | return {"status": "success", "message": "Connection data collected", "data": status} 120 | -------------------------------------------------------------------------------- /src/opnsense/scripts/ThreatPatrols/Autossh/autossh/utils/content_helpers.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2022 Threat Patrols Pty Ltd 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | """ 26 | 27 | import re 28 | import logging 29 | import datetime 30 | import pytz 31 | from typing import Tuple 32 | 33 | from autossh.exceptions import AutosshException 34 | from autossh.vars import __title__ 35 | 36 | 37 | logger = logging.getLogger(__title__) 38 | 39 | 40 | def normalize_timestamp(timestring: Tuple[str, int, float], target_timezone=None) -> str: 41 | """ 42 | Takes the supplied timestring and normalizes it into format %Y-%m-%d %H:%M:%S optionally 43 | adjusted into the requested target_timezone 44 | 45 | NB: Handles a limited set of possible input timestring formats only. 46 | 47 | :param timestring: 48 | :param target_timezone: 49 | :return: 50 | """ 51 | 52 | def datetime_from_timestring(ts): 53 | 54 | # 2018-08-14 07:28:05+00:00 55 | # 2022-06-20 22:50:49.347000+00:00 56 | t = re.compile("^(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d)[T| ](\\d\\d):(\\d\\d):(\\d+).*?\\+00:00").findall(ts) 57 | if t: 58 | std = f"{t[0][0]}-{t[0][1]}-{t[0][2]} {t[0][3]}:{t[0][4]}:{t[0][5]} +0000" 59 | return datetime.datetime.strptime(std, "%Y-%m-%d %H:%M:%S %z") 60 | 61 | # 2018-08-04T07:46:37.000Z 62 | # 2018-08-04T07:44:45Z 63 | t = re.compile("^(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d)[T| ](\\d\\d):(\\d\\d):(\\d+).*?Z").findall(ts) 64 | if t: 65 | std = f"{t[0][0]}-{t[0][1]}-{t[0][2]} {t[0][3]}:{t[0][4]}:{t[0][5]} +0000" 66 | return datetime.datetime.strptime(std, "%Y-%m-%d %H:%M:%S %z") 67 | 68 | # 20180804T074445Z 69 | # 20180804Z074445 70 | t = re.compile("^(\\d\\d\\d\\d)(\\d\\d)(\\d\\d)[T|Z](\\d\\d)(\\d\\d)(\\d+)[Z]?").findall(ts) 71 | if t: 72 | std = f"{t[0][0]}-{t[0][1]}-{t[0][2]} {t[0][3]}:{t[0][4]}:{t[0][5]} +0000" 73 | return datetime.datetime.strptime(std, "%Y-%m-%d %H:%M:%S %z") 74 | 75 | # 1533378888 76 | # 1533373930.983988 77 | t = re.compile("^(\\d+)(\\.\\d+)?").findall(ts) 78 | if t: 79 | std = datetime.datetime.fromtimestamp(float(f"{t[0][0]}{t[0][1]}")) 80 | return pytz.timezone("UTC").localize(std) 81 | 82 | return None 83 | 84 | datetime_obj = datetime_from_timestring(ts=str(timestring)) 85 | 86 | if not datetime_obj: 87 | raise AutosshException("Unknown timestamp format supplied, unable to normalize", timestring) 88 | 89 | if target_timezone: 90 | datetime_obj = datetime_obj.astimezone(pytz.timezone(target_timezone)) 91 | 92 | return datetime_obj.strftime("%Y-%m-%d %H:%M:%S") 93 | -------------------------------------------------------------------------------- /src/opnsense/scripts/ThreatPatrols/Autossh/autossh/utils/exec_helpers.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2022 Threat Patrols Pty Ltd 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | """ 26 | 27 | import logging 28 | import subprocess 29 | 30 | from autossh.vars import __title__ 31 | from autossh.exceptions import AutosshException 32 | 33 | 34 | logger = logging.getLogger(__title__) 35 | 36 | 37 | def get_pid_command(pid: int) -> str: 38 | logger.debug(f"get_pid_command(pid={pid})") 39 | stdout, stderr, rc = exec_command("ps -o command= -p {}".format(int(pid))) 40 | 41 | if stderr: 42 | raise AutosshException(stderr) 43 | 44 | return stdout.decode("utf8") 45 | 46 | 47 | def exec_command(command_line: str, timeout=10) -> tuple: 48 | """ 49 | Execute a shell command and return the stdout, stderr and exit-code 50 | 51 | :param command_line: str 52 | :param timeout: int 53 | :return: 54 | """ 55 | logger.debug(f"exec_command(command_line={command_line}, timeout={timeout})") 56 | try: 57 | sp = subprocess.run(command_line, shell=True, capture_output=True, timeout=timeout, check=False) 58 | except subprocess.TimeoutExpired as e: 59 | raise AutosshException(e) from e 60 | 61 | logger.debug( 62 | f"exec_command() -> stdout= " 63 | f"stderr= returncode={sp.returncode}" 64 | ) 65 | return sp.stdout, sp.stderr, sp.returncode 66 | -------------------------------------------------------------------------------- /src/opnsense/scripts/ThreatPatrols/Autossh/autossh/utils/file_helpers.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2022 Threat Patrols Pty Ltd 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | """ 26 | 27 | import os 28 | import logging 29 | 30 | from .exec_helpers import exec_command 31 | 32 | from autossh.exceptions import AutosshException 33 | from autossh.vars import __title__ 34 | 35 | 36 | logger = logging.getLogger(__title__) 37 | 38 | 39 | def secure_delete(filename: str) -> True: 40 | """ 41 | Deletes a file by overwriting the first {bytes_overwrite} bytes with 42 | data from /dev/urandom, then performing a regular file unlink 43 | """ 44 | logger.debug(f"secure_delete(filename={filename})") 45 | 46 | if not os.path.isfile(filename): 47 | raise AutosshException("Unable to find file for secure_delete()", filename) 48 | 49 | bytes_to_overwrite = int(os.path.getsize(filename)) + (4096 * 32) # 128K == default ZFS record size 50 | 51 | command_line = f"head -c{bytes_to_overwrite} /dev/urandom > {filename}" 52 | stdout, stderr, rc = exec_command(command_line) 53 | if stderr or rc > 0: 54 | raise AutosshException("Unable to overwrite file for secure_delete()", stderr) 55 | 56 | os.unlink(filename) 57 | if os.path.isfile(filename): 58 | raise AutosshException("File still remains after being unlinked") 59 | 60 | return True 61 | -------------------------------------------------------------------------------- /src/opnsense/scripts/ThreatPatrols/Autossh/autossh/utils/host_keys.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2022 Threat Patrols Pty Ltd 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | """ 26 | 27 | import os 28 | import logging 29 | 30 | from autossh.exceptions import AutosshException 31 | from autossh.vars import __title__ 32 | 33 | 34 | logger = logging.getLogger(__title__) 35 | 36 | 37 | def host_keys(connection_uuid, filemask="/var/db/autossh/{}.known_hosts") -> dict: 38 | logger.debug(f"host_keys(connection_uuid={connection_uuid}, filemask={filemask})") 39 | 40 | filename = filemask.format(connection_uuid) 41 | if not os.path.isfile(filename): 42 | raise AutosshException(f"No known_hosts file found for {connection_uuid}; is this a new connection?") 43 | 44 | with open(filename, "r") as f: 45 | known_host_lines = f.readlines() 46 | 47 | known_host_keys = [] 48 | for known_host_line in known_host_lines: 49 | values = known_host_line.rstrip().split(" ") 50 | values.pop(0) 51 | known_host_keys.append(" ".join(values)) 52 | 53 | return { 54 | "status": "success", 55 | "message": f"Found {len(known_host_keys)} host keys found for {connection_uuid}", 56 | "data": known_host_keys, 57 | } 58 | -------------------------------------------------------------------------------- /src/opnsense/scripts/ThreatPatrols/Autossh/autossh/utils/key_gen.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2022 Threat Patrols Pty Ltd 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | """ 26 | 27 | import os 28 | import time 29 | import base64 30 | import logging 31 | 32 | from .random_helpers import random_chars 33 | from .system_helpers import get_system_hostid 34 | from .exec_helpers import exec_command 35 | from .file_helpers import secure_delete 36 | 37 | from autossh.exceptions import AutosshException 38 | from autossh.vars import __title__ 39 | 40 | 41 | logger = logging.getLogger(__title__) 42 | 43 | 44 | def key_gen(key_type, fingerprint_type="sha256"): 45 | logger.debug(f"key_gen(key_type={key_type}, fingerprint_type={fingerprint_type})") 46 | 47 | temp_keyfile = os.path.join( 48 | "/tmp", 49 | f"autossh-keygen.{random_chars(8)}", 50 | ) 51 | 52 | key_comment = f"host_id:{get_system_hostid()}" 53 | 54 | key_generators = { 55 | "dsa1024": f'ssh-keygen -t dsa -b 1024 -q -N "" -f "{temp_keyfile}" -C "{key_comment}"', 56 | "ecdsa256": f'ssh-keygen -t ecdsa -b 256 -q -N "" -f "{temp_keyfile}" -C "{key_comment}"', 57 | "ecdsa384": f'ssh-keygen -t ecdsa -b 384 -q -N "" -f "{temp_keyfile}" -C "{key_comment}"', 58 | "ecdsa521": f'ssh-keygen -t ecdsa -b 521 -q -N "" -f "{temp_keyfile}" -C "{key_comment}"', 59 | "ed25519": f'ssh-keygen -t ed25519 -q -N "" -f "{temp_keyfile}" -C "{key_comment}"', 60 | "rsa1024": f'ssh-keygen -t rsa -b 1024 -q -N "" -f "{temp_keyfile}" -C "{key_comment}"', 61 | "rsa2048": f'ssh-keygen -t rsa -b 2048 -q -N "" -f "{temp_keyfile}" -C "{key_comment}"', 62 | "rsa4096": f'ssh-keygen -t rsa -b 4096 -q -N "" -f "{temp_keyfile}" -C "{key_comment}"', 63 | } 64 | 65 | if key_type not in key_generators.keys(): 66 | raise AutosshException("key_type not supported", key_type) 67 | 68 | exec_command(key_generators[key_type]) 69 | if not os.path.isfile(temp_keyfile) or not os.path.isfile(temp_keyfile + ".pub"): 70 | raise AutosshException("unable to correctly generate ssh material") 71 | 72 | fingerprint, stderr, rc = exec_command( 73 | 'ssh-keygen -f "{}" -l -E {}'.format(temp_keyfile + ".pub", fingerprint_type) 74 | ) 75 | if stderr or rc > 0: 76 | raise AutosshException("unable to correctly generate ssh key signature", stderr) 77 | 78 | with open(temp_keyfile, "rb") as f: 79 | private_key_data = f.read() 80 | secure_delete(temp_keyfile) 81 | 82 | with open(temp_keyfile + ".pub", "rb") as f: 83 | public_key_data = f.read() 84 | secure_delete(temp_keyfile + ".pub") 85 | 86 | return { 87 | "status": "success", 88 | "message": "SSH keypair successfully created", 89 | "data": { 90 | "key_private": base64.b64encode(private_key_data).decode("utf8"), 91 | "key_public": base64.b64encode(public_key_data).decode("utf8"), 92 | "key_fingerprint": fingerprint.decode("utf8").strip(), 93 | "timestamp": time.time(), 94 | }, 95 | } 96 | -------------------------------------------------------------------------------- /src/opnsense/scripts/ThreatPatrols/Autossh/autossh/utils/logger_helpers.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2022 Threat Patrols Pty Ltd 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | """ 26 | 27 | import logging 28 | import logging.handlers 29 | import platform 30 | 31 | from autossh.exceptions import AutosshException 32 | from autossh.vars import __title__ 33 | 34 | CONSOLE_LOGGING_FORMAT = "%(asctime)s %(name)s[%(process)d] %(levelname)s: %(message)s" 35 | SYSLOG_LOGGING_FORMAT = "%(name)s[%(process)d] %(levelname)s: %(message)s" 36 | LOGFILE_LOGGING_FORMAT = "%(asctime)s __hostname__ %(name)s[%(process)d] %(levelname)s: %(message)s" 37 | LOGGING_DATE_FORMAT = "%Y-%m-%dT%H:%M:%S%z" 38 | 39 | 40 | logger = logging.getLogger(__title__) 41 | 42 | 43 | def init_logger(console_level=None, syslog_level=None, logfile_level=None, logfile_filepath=None): 44 | 45 | # remove any existing handlers 46 | for handler in logger.handlers: 47 | logger.removeHandler(handler) 48 | 49 | console_level = __init_console_handler(console_level) 50 | syslog_level = __init_syslog_handler(syslog_level) 51 | logfile_level = __init_logfile_handler(logfile_level, logfile_filepath) 52 | 53 | # set the minimum log level 54 | logger.setLevel(level=min(console_level, syslog_level, logfile_level)) 55 | 56 | 57 | def __init_console_handler(console_level): 58 | 59 | if not console_level: 60 | return 100 61 | 62 | log_level = logging.getLevelName(console_level.upper()) 63 | try: 64 | int(log_level) 65 | except ValueError: 66 | raise AutosshException(f"Unknown console_level:{console_level} requested") 67 | 68 | logging_format = logging.Formatter( 69 | fmt=CONSOLE_LOGGING_FORMAT.replace("__hostname__", str(platform.node()).split(".", maxsplit=1)[0]), 70 | datefmt=LOGGING_DATE_FORMAT, 71 | ) 72 | 73 | console_handler = logging.StreamHandler() 74 | console_handler.setLevel(log_level) 75 | console_handler.setFormatter(logging_format) 76 | logger.addHandler(console_handler) 77 | 78 | return log_level 79 | 80 | 81 | def __init_syslog_handler(syslog_level): 82 | 83 | if not syslog_level: 84 | return 100 85 | 86 | log_level = logging.getLevelName(syslog_level.upper()) 87 | try: 88 | int(log_level) 89 | except ValueError: 90 | raise AutosshException(f"Unknown syslog_level:{syslog_level} requested") 91 | 92 | logging_format = logging.Formatter( 93 | fmt=SYSLOG_LOGGING_FORMAT.replace("__hostname__", str(platform.node()).split(".", maxsplit=1)[0]), 94 | datefmt=LOGGING_DATE_FORMAT, 95 | ) 96 | 97 | if platform.system().lower().endswith("bsd"): 98 | syslog_address = "/var/run/log" 99 | else: 100 | syslog_address = "/dev/log" 101 | 102 | syslog_handler = logging.handlers.SysLogHandler(address=syslog_address) 103 | syslog_handler.setLevel(log_level) 104 | syslog_handler.setFormatter(logging_format) 105 | logger.addHandler(syslog_handler) 106 | 107 | return log_level 108 | 109 | 110 | def __init_logfile_handler(logfile_level, logfile_filepath): 111 | 112 | if not logfile_level or not logfile_filepath: 113 | return 100 114 | 115 | log_level = logging.getLevelName(logfile_level.upper()) 116 | try: 117 | int(log_level) 118 | except ValueError: 119 | raise AutosshException(f"Unknown logfile_level:{logfile_level} requested") 120 | 121 | logging_format = logging.Formatter( 122 | fmt=LOGFILE_LOGGING_FORMAT.replace("__hostname__", str(platform.node()).split(".", maxsplit=1)[0]), 123 | datefmt=LOGGING_DATE_FORMAT, 124 | ) 125 | 126 | try: 127 | file_handler = logging.FileHandler(filename=logfile_filepath) 128 | file_handler.setLevel(log_level) 129 | file_handler.setFormatter(logging_format) 130 | logger.addHandler(file_handler) 131 | except Exception: # noqa 132 | logger.warning(f"Unable to write to logfile at: {logfile_filepath}") 133 | 134 | return log_level 135 | -------------------------------------------------------------------------------- /src/opnsense/scripts/ThreatPatrols/Autossh/autossh/utils/random_helpers.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2022 Threat Patrols Pty Ltd 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | """ 26 | 27 | import random 28 | 29 | 30 | def random_float(minimum_value=0.0, maximum_value=1.0, decimal_places=4) -> float: 31 | """ 32 | Returns a pseudo random float between the min/max and with the required number of decimal places 33 | 34 | :param minimum_value: 35 | :param maximum_value: 36 | :param decimal_places: 37 | :return: 38 | """ 39 | return round(random.uniform(minimum_value, maximum_value), decimal_places) 40 | 41 | 42 | def random_chars(length=8) -> str: 43 | """ 44 | Returns a pseudo random string with lowercase-chars and numbers, with the required length of chars 45 | 46 | :param length: 47 | :return: 48 | """ 49 | chars = "abcdefghijklmnopqrstuvwxyz0123456789" 50 | return "".join(random.choice(chars) for _ in range(length)) 51 | -------------------------------------------------------------------------------- /src/opnsense/scripts/ThreatPatrols/Autossh/autossh/utils/system_helpers.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2022 Threat Patrols Pty Ltd 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | """ 26 | 27 | import os 28 | import logging 29 | import platform 30 | 31 | from autossh.vars import __title__ 32 | from autossh.vars import __system_hostid_file__ 33 | 34 | 35 | logger = logging.getLogger(__title__) 36 | 37 | 38 | def get_system_hostid() -> str: 39 | """ 40 | Reads the system hostid file and returns this value if exists, else returns a dummy zero'd value 41 | 42 | :return: 43 | """ 44 | logger.debug("get_system_hostid()") 45 | 46 | hostid = "00000000-0000-0000-0000-000000000000" 47 | if os.path.isfile(__system_hostid_file__): 48 | with open(__system_hostid_file__, "rb") as f: 49 | data = f.read() 50 | if len(data) > 30: 51 | hostid = data.decode("utf8").strip() 52 | return hostid 53 | 54 | 55 | def get_system_hostname() -> str: 56 | """ 57 | Acquires the system hostname via the Python platform.node() function 58 | 59 | :return: 60 | """ 61 | logger.debug("get_system_hostname()") 62 | 63 | return str(platform.node()).split(".", maxsplit=1)[0] 64 | -------------------------------------------------------------------------------- /src/opnsense/scripts/ThreatPatrols/Autossh/autossh/vars.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2022 Threat Patrols Pty Ltd 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | """ 26 | 27 | __title__ = "autossh" 28 | 29 | __logging_syslog_level__ = "info" 30 | __logging_console_level__ = "info" 31 | 32 | __system_ps_bin__ = "/bin/ps" 33 | __system_hostid_file__ = "/etc/hostid" 34 | __system_config_file__ = "/conf/config.xml" 35 | 36 | __autossh_config_file__ = "/usr/local/etc/autossh/autossh.conf" 37 | __autossh_data_model_file__ = "/usr/local/opnsense/mvc/app/models/ThreatPatrols/Autossh/Autossh.xml" 38 | __autossh_logfile__ = "/var/log/autossh/latest.log" 39 | -------------------------------------------------------------------------------- /src/opnsense/scripts/ThreatPatrols/Autossh/tests/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | sys.path.append(os.path.join(os.path.dirname(__file__), "..", "autossh")) 5 | -------------------------------------------------------------------------------- /src/opnsense/scripts/ThreatPatrols/Autossh/tests/test_content_helpers.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | try: 5 | from autossh.exceptions import AutosshException 6 | except ModuleNotFoundError: 7 | sys.path.append(os.path.join(os.path.dirname(__file__), "..")) 8 | from autossh.exceptions import AutosshException 9 | 10 | from autossh.utils.content_helpers import normalize_timestamp 11 | 12 | 13 | def test_normalize_timestamp_01(): 14 | 15 | timezone = "US/Central" 16 | timestring = "2018-08-04T07:46:37.000Z" 17 | response = normalize_timestamp(timestring=timestring, target_timezone=timezone) 18 | 19 | assert response == "2018-08-04 02:46:37" 20 | 21 | 22 | def test_normalize_timestamp_02(): 23 | 24 | timezone = "Asia/Manila" 25 | timestring = "2018-08-14 07:28:05+00:00" 26 | response = normalize_timestamp(timestring=timestring, target_timezone=timezone) 27 | 28 | assert response == "2018-08-14 15:28:05" 29 | 30 | 31 | def test_normalize_timestamp_03(): 32 | 33 | timezone = "Europe/Berlin" 34 | timestring = "2018-08-04T07:44:45Z" 35 | response = normalize_timestamp(timestring=timestring, target_timezone=timezone) 36 | 37 | assert response == "2018-08-04 09:44:45" 38 | 39 | 40 | def test_normalize_timestamp_04(): 41 | 42 | timezone = "Europe/London" 43 | timestring = "20180802T074245Z" 44 | response = normalize_timestamp(timestring=timestring, target_timezone=timezone) 45 | 46 | assert response == "2018-08-02 08:42:45" 47 | 48 | 49 | def test_normalize_timestamp_05(): 50 | 51 | timezone = "Australia/Perth" 52 | timestring = "20180804Z074445" 53 | response = normalize_timestamp(timestring=timestring, target_timezone=timezone) 54 | 55 | assert response == "2018-08-04 15:44:45" 56 | 57 | 58 | def test_normalize_timestamp_06(): 59 | 60 | timezone = "Asia/Singapore" 61 | timestring = 1533373930.983988 62 | response = normalize_timestamp(timestring=timestring, target_timezone=timezone) 63 | 64 | assert response == "2018-08-05 03:12:10" 65 | 66 | 67 | def test_normalize_timestamp_07(): 68 | 69 | timezone = "Asia/Jakarta" 70 | timestring = 1533378888 71 | response = normalize_timestamp(timestring=timestring, target_timezone=timezone) 72 | 73 | assert response == "2018-08-05 03:34:48" 74 | 75 | 76 | def test_normalize_timestamp_08(): 77 | 78 | timestring = 1533378888 79 | response = normalize_timestamp(timestring=timestring) 80 | 81 | assert response == "2018-08-04 20:34:48" 82 | 83 | 84 | def test_normalize_timestamp_09(): 85 | 86 | timestring = "2018-08-04T20:34:48Z" 87 | response = normalize_timestamp(timestring=timestring) 88 | 89 | assert response == "2018-08-04 20:34:48" 90 | -------------------------------------------------------------------------------- /src/opnsense/scripts/ThreatPatrols/Autossh/tests/test_exec_helpers.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import pytest 4 | 5 | try: 6 | from autossh.exceptions import AutosshException 7 | except ModuleNotFoundError: 8 | sys.path.append(os.path.join(os.path.dirname(__file__), "..")) 9 | from autossh.exceptions import AutosshException 10 | 11 | from autossh.utils.exec_helpers import exec_command 12 | 13 | 14 | def test_exec_with_timeout_exception01(): 15 | command_line = "sleep 3" 16 | 17 | with pytest.raises(AutosshException) as exception_info: 18 | exec_command(command_line=command_line, timeout=1) 19 | 20 | assert "TimeoutExpired" in str(exception_info) 21 | 22 | 23 | def test_exec_simple_command(): 24 | 25 | command_line = "ls -al" 26 | stdout, stderr, returncode = exec_command(command_line=command_line) 27 | 28 | assert isinstance(stdout, bytes) is True 29 | assert isinstance(stderr, bytes) is True 30 | assert len(stderr) == 0 31 | assert returncode == 0 32 | -------------------------------------------------------------------------------- /src/opnsense/scripts/ThreatPatrols/Autossh/tests/test_random_helpers.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | try: 5 | from autossh.exceptions import AutosshException 6 | except ModuleNotFoundError: 7 | sys.path.append(os.path.join(os.path.dirname(__file__), "..")) 8 | from autossh.exceptions import AutosshException 9 | 10 | from autossh.utils.random_helpers import random_chars 11 | from autossh.utils.random_helpers import random_float 12 | 13 | 14 | def test_random_chars(): 15 | 16 | response = random_chars(length=30) 17 | assert len(response) == 30 18 | 19 | response = random_chars() 20 | assert len(response) == 8 21 | 22 | 23 | def test_random_float(): 24 | 25 | response = random_float(minimum_value=0.1, maximum_value=0.9) 26 | assert response >= 0.1 27 | assert response <= 0.9 28 | -------------------------------------------------------------------------------- /src/opnsense/scripts/ThreatPatrols/Autossh/tests/test_system_helpers.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | try: 5 | from autossh.exceptions import AutosshException 6 | except ModuleNotFoundError: 7 | sys.path.append(os.path.join(os.path.dirname(__file__), "..")) 8 | from autossh.exceptions import AutosshException 9 | 10 | from autossh.utils.system_helpers import get_system_hostid 11 | from autossh.utils.system_helpers import get_system_hostname 12 | from autossh.utils.random_helpers import random_chars 13 | 14 | 15 | def test_get_system_hostname(): 16 | 17 | response = get_system_hostname() 18 | assert response is not None 19 | assert len(response) > 1 20 | assert isinstance(response, str) 21 | 22 | 23 | def test_get_system_hostid01(monkeypatch): 24 | 25 | filename = "/tmp/autossh_test.hostid" 26 | monkeypatch.setattr("autossh.vars.__system_hostid_file__", filename) 27 | 28 | faux_hostid = random_chars(length=20) 29 | 30 | with open(filename, "w") as f: 31 | f.write(faux_hostid) 32 | 33 | hostid = get_system_hostid() 34 | assert hostid == "00000000-0000-0000-0000-000000000000" # occurs because length of hostid is not 36 35 | 36 | os.unlink(filename) 37 | 38 | 39 | def test_get_system_hostid02(monkeypatch): 40 | 41 | filename = "/tmp/autossh_test.hostid" 42 | monkeypatch.setattr("autossh.utils.system_helpers.__system_hostid_file__", filename) 43 | 44 | faux_hostid = random_chars(length=36) 45 | 46 | with open(filename, "w") as f: 47 | f.write(faux_hostid) 48 | 49 | hostid = get_system_hostid() 50 | assert hostid == faux_hostid 51 | 52 | os.unlink(filename) 53 | -------------------------------------------------------------------------------- /src/opnsense/service/conf/actions.d/actions_autossh.conf: -------------------------------------------------------------------------------- 1 | 2 | [start_tunnel] 3 | command:/usr/local/etc/rc.d/opnsense-autossh start_tunnel 4 | parameters:%s 5 | type:script_output 6 | message:Starting autossh tunnel 7 | 8 | [stop_tunnel] 9 | command:/usr/local/etc/rc.d/opnsense-autossh stop_tunnel 10 | parameters:%s 11 | type:script_output 12 | message:Stopping autossh tunnel 13 | 14 | [restart_tunnel] 15 | command:/usr/local/etc/rc.d/opnsense-autossh restart_tunnel 16 | parameters:%s 17 | type:script_output 18 | message:Restarting autossh tunnel 19 | 20 | [status_tunnel] 21 | command:/usr/local/etc/rc.d/opnsense-autossh status_tunnel 22 | parameters:%s 23 | type:script_output 24 | message:Obtaining autossh tunnel status 25 | 26 | [config_helper] 27 | command:/usr/local/opnsense/scripts/ThreatPatrols/Autossh/autossh.py config_helper 28 | parameters: 29 | type:script_output 30 | message:Config help processing the autossh configurations 31 | 32 | [key_gen] 33 | command:/usr/local/opnsense/scripts/ThreatPatrols/Autossh/autossh.py key_gen 34 | parameters:--key_type=%s 35 | type:script_output 36 | message:Generating ssh-key for Autossh 37 | 38 | [host_keys] 39 | command:/usr/local/opnsense/scripts/ThreatPatrols/Autossh/autossh.py host_keys 40 | parameters:--connection_uuid=%s 41 | type:script_output 42 | message:Getting ssh-server known host keys for Autossh 43 | 44 | [connection_status] 45 | command:/usr/local/opnsense/scripts/ThreatPatrols/Autossh/autossh.py connection_status 46 | parameters:--connection_uuid=%s 47 | type:script_output 48 | message:Collecting connection status data 49 | 50 | [version] 51 | command:/usr/local/opnsense/scripts/ThreatPatrols/Autossh/autossh.py version 52 | parameters: 53 | type:script_output 54 | message:Getting the autossh application version 55 | -------------------------------------------------------------------------------- /src/opnsense/service/templates/ThreatPatrols/Autossh/+TARGETS: -------------------------------------------------------------------------------- 1 | autossh:/etc/rc.conf.d/autossh 2 | autossh.conf:/usr/local/etc/autossh/autossh.conf 3 | syslog-ng-autossh.conf:/usr/local/etc/syslog-ng.conf.d/syslog-ng-autossh.conf -------------------------------------------------------------------------------- /src/opnsense/service/templates/ThreatPatrols/Autossh/autossh: -------------------------------------------------------------------------------- 1 | # 2 | # /etc/rc.conf.d/autossh 3 | # 4 | # This file is automatically generated, do not manually edit as changes *will* be lost! 5 | # 6 | 7 | autossh_enable="YES" 8 | autossh_loglevel=4 # NB: loglevel:4 is the minimum level that provides the "connection ok" data for health-state 9 | autossh_monitor_port_min=60000 10 | autossh_monitor_port_max=65000 11 | 12 | autossh_monitor_poll_interval=60 # NB: autossh_monitor_poll_interval must be 60 seconds or less 13 | autossh_configfile="/usr/local/etc/autossh/autossh.conf" 14 | -------------------------------------------------------------------------------- /src/opnsense/service/templates/ThreatPatrols/Autossh/autossh.conf: -------------------------------------------------------------------------------- 1 | {# Macro import #} 2 | {% from 'OPNsense/Macros/interface.macro' import physical_interface %} 3 | # 4 | # /usr/local/etc/autossh/autossh.conf 5 | # 6 | # This file is automatically generated, do not manually edit as changes *will* be lost! 7 | # 8 | # Reference: http://man.openbsd.org/OpenBSD-current/man5/ssh_config.5 9 | # 10 | 11 | {% if helpers.exists('ThreatPatrols.Autossh.tunnels.tunnel') %} 12 | {% for tunnel_item in helpers.toList('ThreatPatrols.Autossh.tunnels.tunnel') %} 13 | {% for uuid in helpers._template_in_data['__uuid__'] %} 14 | {% if helpers._template_in_data['__uuid__'][uuid] == tunnel_item %} 15 | {% set tunnel = helpers.getUUID(uuid) %} 16 | {% if tunnel.enabled == "1" %} 17 | 18 | Host {{ uuid }} 19 | 20 | AddKeysToAgent no 21 | AddressFamily {{ tunnel.address_family }} 22 | BatchMode yes 23 | BindInterface {{ physical_interface(tunnel.bind_interface) }} 24 | CanonicalizeHostname no 25 | ChallengeResponseAuthentication yes 26 | CheckHostIP {{ tunnel.check_host_ip }} 27 | Ciphers {{ tunnel.ciphers }} 28 | ClearAllForwardings no 29 | Compression {{ tunnel.compression }} 30 | ConnectionAttempts {{ tunnel.connection_attempts }} 31 | ConnectTimeout {{ tunnel.connect_timeout }} 32 | ControlMaster no 33 | ControlPath none 34 | ControlPersist no 35 | {% if tunnel.dynamic_forward %} 36 | DynamicForward {{ tunnel.dynamic_forward }} 37 | {% endif %} 38 | EnableSSHKeysign no 39 | ExitOnForwardFailure yes 40 | FingerprintHash sha256 41 | ForwardAgent no 42 | ForwardX11 no 43 | ForwardX11Trusted no 44 | GatewayPorts {{ tunnel.gateway_ports }} 45 | GlobalKnownHostsFile /dev/null 46 | HashKnownHosts no 47 | HostbasedAuthentication no 48 | HostKeyAlgorithms {{ tunnel.host_key_algorithms }} 49 | HostKeyAlias {{ uuid }} 50 | HostName {{ tunnel.hostname }} 51 | IdentitiesOnly yes 52 | IdentityAgent none 53 | IdentityFile /var/db/autossh/{{ uuid }}.identity 54 | IPQoS none 55 | KbdInteractiveAuthentication no 56 | KexAlgorithms {{ tunnel.kex_algorithms }} 57 | LocalCommand printf "connection: %r@%h:%p\ntimestamp: `date +%%s`\ntunnel_device: %T\n" > /var/run/autossh.{{ uuid }}.info 58 | {% if tunnel.local_forward %} 59 | LocalForward {{ tunnel.local_forward }} 60 | {% endif %} 61 | LogLevel {{ tunnel.log_level }} 62 | MACs {{ tunnel.macs }} 63 | NoHostAuthenticationForLocalhost no 64 | PasswordAuthentication no 65 | PermitLocalCommand yes 66 | Port {{ tunnel.port|default("22") }} 67 | PreferredAuthentications publickey 68 | # ProxyCommand [todo] 69 | # ProxyJump [todo] 70 | # ProxyUseFdpass [todo] 71 | PubkeyAcceptedKeyTypes {{ tunnel.pubkey_accepted_key_types }} 72 | PubkeyAuthentication yes 73 | RekeyLimit {{ tunnel.rekey_limit }} 74 | {% if tunnel.remote_forward %} 75 | RemoteForward {{ tunnel.remote_forward }} 76 | {% endif %} 77 | RequestTTY no 78 | ServerAliveCountMax {{ tunnel.server_alive_count_max }} 79 | ServerAliveInterval {{ tunnel.server_alive_interval }} 80 | StreamLocalBindUnlink no 81 | StrictHostKeyChecking {{ tunnel.strict_host_key_checking }} 82 | TCPKeepAlive {{ tunnel.tcp_keep_alive }} 83 | # Tunnel no [todo] 84 | # TunnelDevice any:any [todo] 85 | UpdateHostKeys {{ tunnel.update_host_keys }} 86 | User {{ tunnel.user }} 87 | UserKnownHostsFile /var/db/autossh/{{ uuid }}.known_hosts 88 | VerifyHostKeyDNS {{ tunnel.verify_host_key_dns|default("no") }} 89 | VisualHostKey no 90 | 91 | {% endif %} 92 | {% break %} 93 | {% endif %} 94 | {% endfor %} 95 | {% endfor %} 96 | {% endif %} 97 | -------------------------------------------------------------------------------- /src/opnsense/service/templates/ThreatPatrols/Autossh/syslog-ng-autossh.conf: -------------------------------------------------------------------------------- 1 | 2 | ################################################################### 3 | # Local syslog-ng configuration filter definition [autossh] 4 | ################################################################### 5 | filter f_local_autossh { 6 | program("autossh") or program("autosshd") or program("/usr/local/bin/ssh"); 7 | }; 8 | 9 | destination d_local_autossh { 10 | file( 11 | "/var/log/autossh/autossh_${YEAR}${MONTH}${DAY}.log" 12 | create-dirs(yes) 13 | flags(syslog-protocol) 14 | ); 15 | }; 16 | 17 | log { 18 | source(s_all); 19 | filter(f_local_autossh); 20 | destination(d_local_autossh); 21 | }; 22 | -------------------------------------------------------------------------------- /tools/devhost-installer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Copyright (c) 2022 Threat Patrols Pty Ltd 5 | # All rights reserved. 6 | # 7 | # Redistribution and use in source and binary forms, with or without modification, 8 | # are permitted provided that the following conditions are met: 9 | # 10 | # 1. Redistributions of source code must retain the above copyright notice, this 11 | # list of conditions and the following disclaimer. 12 | # 13 | # 2. Redistributions in binary form must reproduce the above copyright notice, 14 | # this list of conditions and the following disclaimer in the documentation 15 | # and/or other materials provided with the distribution. 16 | # 17 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 21 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | # 28 | 29 | set -e 30 | 31 | remote_address=${1} 32 | remote_default_user="root" 33 | action=${2} 34 | 35 | if [ -z "${remote_address}" ] || [ -z "${action}" ]; then 36 | echo 'usage: '${0}' ' 37 | exit 1 38 | fi 39 | 40 | if [[ ${remote_address} = *"@"* ]]; then 41 | remote="${remote_address}" 42 | else 43 | remote="${remote_default_user}@${remote_address}" 44 | fi 45 | 46 | ssh_options="-o ControlMaster=auto -o ControlPersist=60s -q" 47 | local_base_path=$(realpath $(dirname ${0})/../src/) 48 | remote_base_path="/usr/local" 49 | 50 | # ============================================================================= 51 | 52 | echo 53 | echo "Variable settings" 54 | echo "===" 55 | echo "remote: ${remote}" 56 | echo "action: ${action}" 57 | echo "ssh_options: ${ssh_options}" 58 | echo "local_base_path: ${local_base_path}" 59 | echo "remote_base_path: ${remote_base_path}" 60 | echo 61 | 62 | # ============================================================================= 63 | 64 | dev_install() 65 | { 66 | 67 | # deploy_files 68 | echo "Deploy autossh files" 69 | echo "===" 70 | cd "${local_base_path}" 71 | for filepath_find in $(find . -type f -not -path */.py* -not -name *.pyc -not -name test_* -not -name .git*); do 72 | filepath=$(echo $filepath_find | sed 's/^..//') 73 | deploy_file "${local_base_path}/${filepath}" "${remote_base_path}/${filepath}" 74 | done 75 | echo 76 | 77 | echo "Rebuild the Autossh templates" 78 | echo "===" 79 | ssh ${ssh_options} ${remote} "configctl template reload ThreatPatrols/Autossh" 80 | echo 81 | 82 | echo "Restart OPNsense configd" 83 | echo "===" 84 | ssh ${ssh_options} ${remote} "/usr/local/etc/rc.configure_plugins; service configd restart" 85 | echo 86 | } 87 | 88 | # ============================================================================= 89 | 90 | dev_uninstall() 91 | { 92 | echo "Remove autossh files" 93 | echo "===" 94 | cd "${local_base_path}" 95 | for filepath_find in $(find . -type f -not -path */.py* -not -name *.pyc -not -name test_* -not -name .git*); do 96 | filepath=$(echo $filepath_find | sed 's/^..//') 97 | remove_file "${remote_base_path}/${filepath}" 98 | done 99 | echo 100 | 101 | echo "Restart OPNsense configd" 102 | echo "===" 103 | ssh ${ssh_options} ${remote} "/usr/local/etc/rc.configure_plugins; service configd restart" 104 | echo 105 | } 106 | 107 | # ============================================================================= 108 | 109 | deploy_file() 110 | { 111 | local src_fullpath=${1} 112 | local dst_fullpath=${2} 113 | echo "deploy: ${src_fullpath/${local_base_path}\//\{base\}/}" 114 | ssh ${ssh_options} "${remote}" "mkdir -p \"\`dirname ${dst_fullpath}\`\"" 115 | scp ${ssh_options} -p "${src_fullpath}" "${remote}:${dst_fullpath}" 116 | } 117 | 118 | # ============================================================================= 119 | 120 | remove_file() 121 | { 122 | local dst_fullpath=${1} 123 | if [[ "${dst_fullpath}" == *"Autossh"* ]]; then 124 | echo "remove-path: ${dst_fullpath}" 125 | ssh ${ssh_options} "${remote}" "rm -Rf \"\`dirname ${dst_fullpath}\`\"" 126 | else 127 | echo "remove-file: ${dst_fullpath}" 128 | ssh ${ssh_options} "${remote}" "rm -f ${dst_fullpath}" 129 | fi 130 | } 131 | 132 | # ============================================================================= 133 | 134 | if [[ "${action}" = "install" ]]; then 135 | dev_install 136 | exit 0 137 | elif [[ "${action}" = "uninstall" ]]; then 138 | dev_uninstall 139 | exit 0 140 | fi 141 | 142 | echo "ERROR: unknown action" 143 | exit 1 144 | --------------------------------------------------------------------------------