编辑
2025-02-26
Firewall
00
请注意,本文编写于 35 天前,最后修改于 35 天前,其中某些信息可能已经过时。

目录

配置流程
添加WebDAV备份插件

本文主要内容

由于OPNsense 24.1默认配置备份只支持本地和Goolge Drive两种备份方式,本文主要是给OPNSense添加WebDAV备份功能。

配置流程

添加WebDAV备份插件

  1. 进入OPNSense,打开安全shell设置,ssh连接进入系统shell。 CleanShot 2025-02-26 at 12.53.16@2x.png
  2. 创建文件夹 mkdir -p /usr/local/opnsense/mvc/app/models/OPNsense/Backup/ 后,添加核心实现逻辑代码。
    bash
    cat << 'EOF' > /usr/local/opnsense/mvc/app/library/OPNsense/Backup/WebDAV.php <?php ///usr/local/opnsense/mvc/app/library/OPNsense/Backup/WebDAV.php /* * Copyright (C) 2018 Deciso B.V. * Copyright (C) 2018 Fabian Franz * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ namespace OPNsense\Backup; use OPNsense\Core\Config; /** * Class WebDAV backup * @package OPNsense\Backup */ class WebDAV extends Base implements IBackupProvider { /** * get required (user interface) fields for backup connector * @return array configuration fields, types and description */ public function getConfigurationFields() { $fields = array( array( "name" => "enabled", "type" => "checkbox", "label" => gettext("Enable"), "value" => null ), array( "name" => "url", "type" => "text", "label" => gettext("URL"), "help" => gettext("The Base URL to WebDAV without trailing slash. For example: https://dav.example.com"), "value" => null ), array( "name" => "user", "type" => "text", "label" => gettext("User Name"), "help" => gettext("The name you use for logging into your WebDAV account"), "value" => null ), array( "name" => "password", "type" => "password", "label" => gettext("Password"), "help" => gettext("The password you use for logging into your WebDAV account"), "value" => null ), array( "name" => "password_encryption", "type" => "password", "label" => gettext("Encryption Password (Optional)"), "help" => gettext("A password to encrypt your configuration"), "value" => null ), array( "name" => "backupdir", "type" => "text", "label" => gettext("Directory Name without leading slash, starting from user's root"), "value" => 'OPNsense-Backup' ) ); $webdav = new WebDAVSettings(); foreach ($fields as &$field) { $field['value'] = (string)$webdav->getNodeByReference($field['name']); } return $fields; } /** * backup provider name * @return string user friendly name */ public function getName() { return gettext("WebDAV"); } /** * validate and set configuration * @param array $conf configuration array * @return array of validation errors when not saved * @throws \OPNsense\Base\ModelException * @throws \ReflectionException */ public function setConfiguration($conf) { $webdav = new WebDAVSettings(); $this->setModelProperties($webdav, $conf); $validation_messages = $this->validateModel($webdav); if (empty($validation_messages)) { $webdav->serializeToConfig(); Config::getInstance()->save(); } return $validation_messages; } /** * perform backup * @return array filelist * @throws \OPNsense\Base\ModelException * @throws \ReflectionException */ public function backup() { $cnf = Config::getInstance(); $webdav = new WebDAVSettings(); if ($cnf->isValid() && !empty((string)$webdav->enabled)) { $config = $cnf->object(); $url = (string)$webdav->url; $username = (string)$webdav->user; $password = (string)$webdav->password; $backupdir = (string)$webdav->backupdir; $crypto_password = (string)$webdav->password_encryption; $hostname = $config->system->hostname . '.' . $config->system->domain; $configname = 'config-' . $hostname . '-' . date('Y-m-d_H_i_s') . '.xml'; // backup source data to local strings (plain/encrypted) $confdata = file_get_contents('/conf/config.xml'); if (!empty($crypto_password)) { $confdata = $this->encrypt($confdata, $crypto_password); } // Check if destination directory exists, create (full path) if not try { $this->create_directory($url, $username, $password, $backupdir); } catch (\Exception $e) { return array(); } try { $this->upload_file_content( $url, $username, $password, $backupdir, $configname, $confdata ); // do not list directories return array_filter( $this->listFiles($url, $username, $password, "/$backupdir/", false), function ($filename) { return (substr($filename, -1) !== '/'); } ); } catch (\Exception $e) { return array(); } } } /** * dir listing * @param string $url remote location * @param string $username username * @param string $password password to use * @param string $directory location to list * @param bool $only_dirs only list directories * @return array * @throws \Exception */ public function listFiles($url, $username, $password, $directory = '/', $only_dirs = true) { $result = $this->curl_request( "$url$directory", $username, $password, 'PROPFIND', "Error while fetching filelist from WebDAV '{$directory}' path" ); //remove line breaks from xml string $xml = preg_replace("/\r?\n/", '', $result['response']); // workaround - simplexml seems to be broken when using namespaces - remove them. //$xml = str_replace(['<D:', '</D:', '<d:', '</d:', '<lp1:', '</lp1:'], ['<', '</', '<', '</', '<', '</'], $xml); // better workaround: remove any namespace $xml = preg_replace('/\s+xmlns:[^=]+="[^"]*"/', '', $xml); $xml = preg_replace('/\b(\w+)\:/i', '', $xml); $xml = simplexml_load_string($xml); $ret = array(); //parse URL for a check if path exists $parsedUrl = parse_url($url); foreach ($xml->children() as $response) { // d:response if ($response->getName() == 'response') { $fileurl = (string)$response->href; //check if URL has a path if (isset($parsedUrl['path'])) { //URL DOES have a path - extracting path from fileurl $dirname = explode($parsedUrl['path'], $fileurl, 2)[1]; } else { //URL does NOT have a path $dirname = $fileurl; } if ( $response->propstat->prop->resourcetype->children()->count() > 0 && $response->propstat->prop->resourcetype->children()[0]->getName() == 'collection' && $only_dirs ) { $ret[] = $dirname; } elseif (!$only_dirs) { $ret[] = $dirname; } } } return $ret; } /** * upload file * @param string $url remote location * @param string $username remote user * @param string $password password to use * @param string $backupdir remote directory * @param string $filename filename to use * @param string $local_file_content contents to save * @throws \Exception when upload fails */ public function upload_file_content($url, $username, $password, $backupdir, $filename, $local_file_content) { $this->curl_request( $url . "/$backupdir/$filename", $username, $password, 'PUT', 'cannot execute PUT', $local_file_content ); } /** * create new remote directory if doesn't exist * @param string $url remote location * @param string $username remote user * @param string $password password to use * @param string $backupdir remote directory * @throws \Exception when create dir fails */ public function create_directory($url, $username, $password, $backupdir) { $parent_path = dirname($backupdir); try { $directories = $this->listFiles($url, $username, $password, "/{$parent_path}"); } catch (\Exception $e) { if ($backupdir == ".") { // We cannot create root, if we reached here there's some other problem syslog(LOG_ERR, "Check WebDAV configuration parameters"); return false; } // If error assume dir doesn't exist. Create parent folder if ($this->create_directory($url, $username, $password, $parent_path) === false) { throw new \Exception(); } } // if path exists ok if (in_array("/{$backupdir}/", $directories)) { return; } // create backupdir, because path does not exist $this->curl_request( $url . "/{$backupdir}", $username, $password, 'MKCOL', 'cannot execute MKCOL' ); } /** * @param string $url remote location * @param string $username remote user * @param string $password password to use * @param string $method http method, PUT, GET, ... * @param string $error_message message to log on failure * @param null|string $postdata http body * @param array $headers HTTP headers * @return array response status * @throws \Exception when request fails */ public function curl_request( $url, $username, $password, $method, $error_message, $postdata = null, $headers = array('User-Agent: OPNsense Firewall') ) { //workaround for Hetzner Storagebox if ($method == "PROPFIND") { array_push($headers, 'Depth: 1'); } $curl = curl_init(); curl_setopt_array($curl, array( CURLOPT_URL => $url, CURLOPT_CUSTOMREQUEST => $method, // Create a file in WebDAV is PUT CURLOPT_RETURNTRANSFER => true, // Do not output the data to STDOUT CURLOPT_VERBOSE => 0, // same here CURLOPT_MAXREDIRS => 0, // no redirects CURLOPT_TIMEOUT => 60, // maximum time: 1 min CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_USERPWD => $username . ":" . $password, CURLOPT_HTTPHEADER => $headers )); if ($postdata != null) { curl_setopt($curl, CURLOPT_POSTFIELDS, $postdata); } $response = curl_exec($curl); $err = curl_error($curl); $info = curl_getinfo($curl); if (!($info['http_code'] == 200 || $info['http_code'] == 207 || $info['http_code'] == 201) || $err) { syslog(LOG_ERR, $error_message); syslog(LOG_ERR, json_encode($info)); throw new \Exception(); } curl_close($curl); return array('response' => $response, 'info' => $info); } /** * Is this provider enabled * @return boolean enabled status * @throws \OPNsense\Base\ModelException * @throws \ReflectionException */ public function isEnabled() { $webdav = new WebDAVSettings(); return (string)$webdav->enabled === "1"; } } 'EOF'
    bash
    cat << 'EOF' > /usr/local/opnsense/mvc/app/models/OPNsense/Backup/WebDAVSettings.php <?php ///usr/local/opnsense/mvc/app/models/OPNsense/Backup/WebDAVSettings.php /** * Copyright (C) 2018 Fabian Franz * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ namespace OPNsense\Backup; use OPNsense\Base\BaseModel; /** * Class WebDAV * @package Backup */ class WebDAVSettings extends BaseModel { } 'EOF'
  3. 添加WebDAVSettings.xml,这一步让在OPNSense备份中显示WebDAV配置界面。
    bash
    cat << 'EOF' > /usr/local/opnsense/mvc/app/models/OPNsense/Backup/WebDAVSettings.xml <model> <mount>//system/backup/webdav</mount> <version>1.0.0</version> <description>OPNsense WebDAV Backup Settings</description> <items> <enabled type="BooleanField"> <default>0</default> <Required>Y</Required> </enabled> <url type="TextField"> <Required>N</Required> <mask>/^https?:\/\/.*[^\/]$/</mask> <ValidationMessage>The URL must be valid without a trailing slash. For example: https://dav.example.com</ValidationMessage> <Constraints> <check001> <ValidationMessage>A URL for the WebDAV server must be set.</ValidationMessage> <type>DependConstraint</type> <addFields> <field1>enabled</field1> </addFields> </check001> </Constraints> </url> <user type="TextField"> <Constraints> <check001> <ValidationMessage>A user for the WebDAV server must be set.</ValidationMessage> <type>DependConstraint</type> <addFields> <field1>enabled</field1> </addFields> </check001> </Constraints> </user> <password type="TextField"> <Constraints> <check001> <ValidationMessage>A password for the WebDAV server must be set.</ValidationMessage> <type>DependConstraint</type> <addFields> <field1>enabled</field1> </addFields> </check001> </Constraints> </password> <password_encryption type="TextField"> <Required>N</Required> </password_encryption> <backupdir type="TextField"> <Required>Y</Required> <mask>/^([\w%+\-]+\/)*[\w+%\-]+$/</mask> <default>OPNsense-Backup</default> <ValidationMessage>The Backup Directory can only consist of alphanumeric characters, dash, underscores and slash. No leading or trailing slash.</ValidationMessage> </backupdir> </items> </model> 'EOF'
  4. 进入OPNSense系统 -> 配置 -> 备份 -> 滑到最后。按需配置即可。 CleanShot 2025-02-26 at 12.59.24@2x.png

本文作者:Lim

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!