本文主要内容
由于OPNsense 24.1
默认配置备份只支持本地和Goolge Drive两种备份方式,本文主要是给OPNSense
添加WebDAV
备份功能。
OPNSense
,打开安全shell
设置,ssh连接进入系统shell。
mkdir -p /usr/local/opnsense/mvc/app/models/OPNsense/Backup/
后,添加核心实现逻辑代码。
bashcat << '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'
bashcat << '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'
WebDAVSettings.xml
,这一步让在OPNSense
备份中显示WebDAV
配置界面。
bashcat << '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'
OPNSense
,系统 -> 配置 -> 备份 -> 滑到最后
。按需配置即可。
本文作者:Lim
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!