stunnel: initial release (#1840)

* stunnel: boilerplate for https://github.com/opnsense/plugins/issues/1829

* stunnel: work in progress for https://github.com/opnsense/plugins/issues/1829

* stunnel: add service control and acl for https://github.com/opnsense/plugins/issues/1829

* stunnel: add cipher selection for https://github.com/opnsense/plugins/issues/1829

Since stunnel uses different parameter pairs for TLSv1.[1,2] and TLSv1.3, we'll try to sort them out in our config template.
When no TLSv1.3 ciphers are allowed, we should limit the sslVersionMax parameter as well as it seems.

* stunnel: set TLS1.2 as minimum

* stunnel: disable rc conf when no services are active https://github.com/opnsense/plugins/issues/1829

* stunnel: CRL support for https://github.com/opnsense/plugins/issues/1829

* stunnel: simplify cert creation, combine cert+key in one file. for https://github.com/opnsense/plugins/issues/1829

* stunnel: syslog and log viewer for https://github.com/opnsense/plugins/issues/1829

* stunnel: add hasync anchor, for https://github.com/opnsense/plugins/issues/1829
This commit is contained in:
Ad Schellevis
2020-05-18 15:31:18 +02:00
committed by GitHub
parent 8611398aaa
commit 2a8b0a58ed
18 changed files with 736 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
PLUGIN_NAME= stunnel
PLUGIN_VERSION= 0.1
PLUGIN_COMMENT= stunnel TLS proxy
PLUGIN_MAINTAINER= ad@opnsense.org
PLUGIN_DEPENDS= stunnel
PLUGIN_DEVEL= yes
.include "../../Mk/plugins.mk"
+2
View File
@@ -0,0 +1,2 @@
Stunnel is a proxy designed to add TLS encryption functionality to existing clients and servers without any changes in the programs' code.
(https://www.stunnel.org/)
@@ -0,0 +1,107 @@
<?php
/*
* Copyright (C) 2020 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
function stunnel_configure()
{
return array(
'crl' => array('stunnel_refresh_crls')
);
}
function stunnel_refresh_crls()
{
$stunnel = new OPNsense\Stunnel\Stunnel();
$configObj = OPNsense\Core\Config::getInstance()->object();
foreach ($stunnel->services->service->__items as $service) {
if (!empty((string)$service->enabled) && !empty((string)$service->enableCRL)) {
foreach (explode(",", (string)$service->cacert) as $cacert) {
$this_ca = null;
if (!empty($configObj->ca)) {
foreach ($configObj->ca as $ca) {
if ((string)$ca->refid == $cacert && !empty((string)$ca->prv)) {
$this_ca = $ca;
}
}
}
if ($this_ca) {
$ca_hash = null;
$ca_crt = base64_decode((string)$this_ca->crt);
$ca_key = base64_decode((string)$this_ca->prv);
$process = proc_open("openssl x509 -hash -noout", [["pipe", "r"], ["pipe", "w"]], $pipes);
if (is_resource($process)) {
fwrite($pipes[0], $ca_crt);
fclose($pipes[0]);
$ca_hash = trim(stream_get_contents($pipes[1]));
fclose($pipes[1]);
proc_close($process);
}
if ($ca_hash) {
$crlres = openssl_crl_new($ca_crt, 0, 9999);
if (!empty($configObj->crl)) {
foreach ($configObj->crl as $crl) {
if ($crl->caref == $cacert && !empty((string)$crl->cert)) {
foreach ($crl->cert as $cert) {
openssl_crl_revoke_cert(
$crlres,
base64_decode((string)$cert->crt),
(string)$cert->revoke_time,
(string)$cert->reason
);
}
}
}
}
$crl_text = "";
openssl_crl_export($crlres, $crl_text, $ca_key);
file_put_contents("/var/run/stunnel/certs/{$ca_hash}.r0", $crl_text);
}
}
}
}
}
}
function stunnel_syslog()
{
$logfacilities = array();
$logfacilities['stunnel'] = array(
'facility' => array('stunnel')
);
return $logfacilities;
}
function stunnel_xmlrpc_sync()
{
$result = array();
$result[] = array(
'description' => gettext('Stunnel'),
'section' => 'OPNsense.Stunnel',
'id' => 'stunnel',
);
return $result;
}
@@ -0,0 +1,42 @@
<?php
/*
* Copyright (c) 2020 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\Stunnel\Api;
use OPNsense\Base\ApiMutableServiceControllerBase;
/**
* {@inheritdoc}
*/
class ServiceController extends ApiMutableServiceControllerBase
{
protected static $internalServiceClass = '\OPNsense\Stunnel\Stunnel';
protected static $internalServiceEnabled = 'general.enabled';
protected static $internalServiceTemplate = 'OPNsense/Stunnel';
protected static $internalServiceName = 'stunnel';
}
@@ -0,0 +1,80 @@
<?php
/*
* Copyright (C) 2020 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\Stunnel\Api;
use \OPNsense\Base\ApiMutableModelControllerBase;
class ServicesController extends ApiMutableModelControllerBase
{
protected static $internalModelName = 'stunnel';
protected static $internalModelClass = 'OPNsense\Stunnel\Stunnel';
protected function save()
{
// hook service enable status on enabled tunnels
$this->getModel()->general->enabled = "0";
foreach ($this->getModel()->services->service->__items as $service) {
if ((string)$service->enabled == "1") {
$this->getModel()->general->enabled = "1";
break;
}
}
parent::save();
}
public function searchItemAction()
{
return $this->searchBase("services.service", array('enabled', 'description'), "description");
}
public function setItemAction($uuid)
{
return $this->setBase("service", "services.service", $uuid);
}
public function addItemAction()
{
return $this->addBase("service", "services.service");
}
public function getItemAction($uuid = null)
{
return $this->getBase("service", "services.service", $uuid);
}
public function delItemAction($uuid)
{
return $this->delBase("services.service", $uuid);
}
public function toggleItemAction($uuid, $enabled = null)
{
return $this->toggleBase("services.service", $uuid, $enabled);
}
}
@@ -0,0 +1,39 @@
<?php
/*
* Copyright (C) 2020 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\Stunnel;
use OPNsense\Base\{IndexController};
class ServicesController extends IndexController
{
public function indexAction()
{
$this->view->pick('OPNsense/Stunnel/services');
$this->view->formDialogService = $this->getForm("dialogService");
}
}
@@ -0,0 +1,69 @@
<form>
<field>
<id>service.enabled</id>
<label>enabled</label>
<type>checkbox</type>
<help>Enable this rule</help>
</field>
<field>
<id>service.accept_address</id>
<label>Listen address</label>
<type>text</type>
<help>If possible, a loopback address is the safest choice here, you can forward traffic to it using the firewall.</help>
</field>
<field>
<id>service.accept_port</id>
<label>Listen port</label>
<type>text</type>
<help>The port on which connections will be accepted.</help>
</field>
<field>
<id>service.connect_address</id>
<label>Target hostname</label>
<type>text</type>
<help>The other end of this tunnel.</help>
</field>
<field>
<id>service.connect_port</id>
<label>Target port</label>
<type>text</type>
<help>The port to forward traffic to.</help>
</field>
<field>
<id>service.servercert</id>
<label>Certificate</label>
<type>dropdown</type>
<help><![CDATA[Select a certificate to use for this service.]]></help>
</field>
<field>
<id>service.cacert</id>
<label>CA to validate connections to</label>
<type>select_multiple</type>
<help><![CDATA[Select a Certificate Authority to validate connections to. To create a CA, go to <a href="/system_camanager.php">CA Manager</a>.]]></help>
</field>
<field>
<id>service.enableCRL</id>
<label>enable CRL</label>
<type>checkbox</type>
<help><![CDATA[
Enable certificate revocation lists, when selected a CRL with the format XXXXXXXX.r0 is required in the chroot (/var/run/stunnel/certs/).
When certificates are managed from this machine, the attached CRLs will be generated automatically.
For more information about this option, see CRLpath in stunnels manual.
If configured and a valid CRL is not available, all connections will be denied.
Additions may need a restart of stunnel (when the certificate was already used).
]]></help>
</field>
<field>
<id>service.ciphers</id>
<label>Ciphers</label>
<type>select_multiple</type>
<help><![CDATA[Select all accepted TLS ciphers.]]></help>
<advanced>true</advanced>
</field>
<field>
<id>service.description</id>
<label>Description</label>
<type>text</type>
</field>
</form>
@@ -0,0 +1,9 @@
<acl>
<page-services-stunnel>
<name>Services: Stunnel</name>
<patterns>
<pattern>ui/stunnel/*</pattern>
<pattern>api/stunnel/*</pattern>
</patterns>
</page-services-stunnel>
</acl>
@@ -0,0 +1,8 @@
<menu>
<VPN>
<Stunnel cssClass="fa fa-dot-circle-o fa-fw" order="110">
<Configuration url="/ui/stunnel/services/"/>
<LogFile VisibleName="Log File" url="/ui/diagnostics/log/core/stunnel"/>
</Stunnel>
</VPN>
</menu>
@@ -0,0 +1,35 @@
<?php
/*
* Copyright (C) 2020 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\Stunnel;
use OPNsense\Base\BaseModel;
class Stunnel extends BaseModel
{
}
@@ -0,0 +1,73 @@
<model>
<mount>//OPNsense/Stunnel</mount>
<version>1.0.0</version>
<migration_prefix>MFP</migration_prefix>
<description>
OPNsense firewall filter rules
</description>
<items>
<general>
<enabled type="BooleanField">
<default>1</default>
<Required>Y</Required>
</enabled>
</general>
<services>
<service type="ArrayField">
<enabled type="BooleanField">
<default>1</default>
<Required>Y</Required>
</enabled>
<accept_port type="IntegerField">
<MinimumValue>1</MinimumValue>
<MaximumValue>65535</MaximumValue>
<ValidationMessage>port needs to be an integer value between 1 and 65535</ValidationMessage>
<Required>Y</Required>
</accept_port>
<accept_address type="NetworkField">
<Required>Y</Required>
<NetMaskAllowed>N</NetMaskAllowed>
<default>127.0.0.1</default>
</accept_address>
<connect_address type="HostnameField">
<Required>Y</Required>
</connect_address>
<connect_port type="IntegerField">
<MinimumValue>1</MinimumValue>
<MaximumValue>65535</MaximumValue>
<ValidationMessage>port needs to be an integer value between 1 and 65535</ValidationMessage>
<Required>Y</Required>
</connect_port>
<cacert type="CertificateField">
<Required>N</Required>
<multiple>Y</multiple>
<Type>ca</Type>
<ValidationMessage>Please select a valid certificate from the list</ValidationMessage>
</cacert>
<enableCRL type="BooleanField">
<default>0</default>
<Required>Y</Required>
</enableCRL>
<ciphers type="JsonKeyValueStoreField">
<default>TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-CHACHA20-POLY1305,ECDHE-RSA-CHACHA20-POLY1305,DHE-RSA-AES128-GCM-SHA256,DHE-RSA-AES256-GCM-SHA384</default>
<Required>Y</Required>
<multiple>Y</multiple>
<ConfigdPopulateAct>stunnel ssl ciphers</ConfigdPopulateAct>
<SourceFile>/tmp/stunnel_ciphers_list.json</SourceFile>
<ConfigdPopulateTTL>360</ConfigdPopulateTTL>
<ValidationMessage>Please specify valid tls ciphers.</ValidationMessage>
</ciphers>
<servercert type="CertificateField">
<Required>Y</Required>
<Type>cert</Type>
<ValidationMessage>Please select a valid certificate from the list</ValidationMessage>
</servercert>
<description type="TextField">
<Required>N</Required>
<mask>/^([\t\n\v\f\r 0-9a-zA-Z.\-,_\x{00A0}-\x{FFFF}]){0,255}$/u</mask>
<ValidationMessage>Description should be a string between 1 and 255 characters</ValidationMessage>
</description>
</service>
</services>
</items>
</model>
@@ -0,0 +1,89 @@
{#
# Copyright (c) 2020 Deciso B.V.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#}
<script>
$( document ).ready(function() {
$("#grid-services").UIBootgrid(
{ search:'/api/stunnel/services/searchItem/',
get:'/api/stunnel/services/getItem/',
set:'/api/stunnel/services/setItem/',
add:'/api/stunnel/services/addItem/',
del:'/api/stunnel/services/delItem/',
toggle:'/api/stunnel/services/toggleItem/'
}
);
$("#reconfigureAct").SimpleActionButton();
updateServiceControlUI('stunnel');
});
</script>
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
<li class="active"><a data-toggle="tab" id="destinations" href="#tab_services">{{ lang._('Services') }}</a></li>
</ul>
<div class="tab-content content-box">
<div id="tab_services" class="tab-pane fade in active">
<!-- tab page "services" -->
<table id="grid-services" class="table table-condensed table-hover table-striped" data-editDialog="DialogService" data-editAlert="stunnelChangeMessage">
<thead>
<tr>
<th data-column-id="uuid" data-type="string" data-identifier="true" data-visible="false">{{ lang._('ID') }}</th>
<th data-column-id="enabled" data-width="6em" data-type="string" data-formatter="rowtoggle">{{ lang._('Enabled') }}</th>
<th data-column-id="description" data-type="string">{{ lang._('Description') }}</th>
<th data-column-id="commands" data-width="7em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<td></td>
<td>
<button data-action="add" type="button" class="btn btn-xs btn-default"><span class="fa fa-plus"></span></button>
<button data-action="deleteSelected" type="button" class="btn btn-xs btn-default"><span class="fa fa-trash-o"></span></button>
</td>
</tr>
</tfoot>
</table>
</div>
<div class="col-md-12">
<div id="stunnelChangeMessage" class="alert alert-info" style="display: none" role="alert">
{{ lang._('After changing settings, please remember to apply them with the button below') }}
</div>
<hr/>
<button class="btn btn-primary" id="reconfigureAct"
data-endpoint='/api/stunnel/service/reconfigure'
data-label="{{ lang._('Apply') }}"
data-service-widget="stunnel"
data-error-title="{{ lang._('Error reconfiguring stunnel') }}"
type="button"
></button>
<br/><br/>
</div>
</div>
{{ partial("layout_partials/base_dialog",['fields':formDialogService, 'id':'DialogService','label':lang._('Edit Service')])}}
@@ -0,0 +1,82 @@
#!/usr/local/bin/php
<?php
/*
* Copyright (C) 2020 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
require_once('plugins.inc');
require_once("legacy_bindings.inc");
use OPNsense\Stunnel\Stunnel;
use OPNsense\Core\Config;
$base_path = "/usr/local/etc/stunnel/certs";
$stunnel = new Stunnel();
$configObj = Config::getInstance()->object();
$all_certs = [];
foreach ($stunnel->services->service->__items as $service) {
if (!empty((string)$service->enabled)) {
$this_uuid = $service->getAttributes()['uuid'];
$srv_certid = (string)$service->servercert;
foreach ($configObj->cert as $cert) {
if ($srv_certid == (string)$cert->refid) {
$all_certs["{$base_path}/{$this_uuid}.crt"] =
base64_decode((string)$cert->crt) . "\n" . base64_decode((string)$cert->prv);
}
}
if (!empty((string)$service->cacert)) {
$all_certs["{$base_path}/{$this_uuid}.ca"] = "";
foreach (explode(",", (string)$service->cacert) as $caid) {
foreach ($configObj->ca as $ca) {
if ((string)$ca->refid == $caid) {
$all_certs["{$base_path}/{$this_uuid}.ca"] .= base64_decode((string)$ca->crt)."\n";
}
}
}
}
}
}
if (!is_dir("/usr/local/etc/stunnel/certs")) {
mkdir("/usr/local/etc/stunnel/certs", 0700, true);
chown("/usr/local/etc/stunnel/certs", "stunnel");
chgrp("/usr/local/etc/stunnel/certs", "stunnel");
}
// cleanup stunnel cert directory
foreach (glob("{$base_path}/*") as $filename) {
if (!isset($all_certs[$filename])) {
unlink($filename);
}
}
foreach($all_certs as $filename => $content) {
file_put_contents($filename, $content);
chown($filename, "stunnel");
}
// trigger certificate revocation lists update
plugins_configure('crl');
@@ -0,0 +1,29 @@
[ssl.ciphers]
command:/usr/local/opnsense/scripts/system/ssl_ciphers.py --format=key_value
parameters:
type:script_output
message:List SSL ciphers
[start]
command:/usr/local/etc/rc.d/stunnel start
parameters:
type:script
message:stunnel service start
[stop]
command:/usr/local/etc/rc.d/stunnel stop
parameters:
type:script
message:stunnel service stop
[restart]
command:/usr/local/etc/rc.d/stunnel restart
parameters:
type:script
message:stunnel service restart
[status]
command:/usr/local/etc/rc.d/stunnel status; exit 0
parameters:
type:script_output
message:stunnel status
@@ -0,0 +1,2 @@
stunnel.conf:/usr/local/etc/stunnel/stunnel.conf
rc.conf.d:/etc/rc.conf.d/stunnel
@@ -0,0 +1,12 @@
{% if not helpers.empty('OPNsense.Stunnel.general.enabled') %}
stunnel_enable="YES"
stunnel_pidfile="/var/run/stunnel/stunnel.pid"
mkdir -p /var/run/stunnel/certs
chown -R stunnel:stunnel /var/run/stunnel
chmod -R 700 /var/run/stunnel
/usr/local/opnsense/scripts/stunnel/generate_certs.php > /dev/null 2>&1
{% else %}
stunnel_enable="NO"
{% endif %}
@@ -0,0 +1,44 @@
setuid = stunnel
setgid = stunnel
chroot = /var/run/stunnel
pid = /stunnel.pid
debug = info
logId = unique
{% if helpers.exists('OPNsense.Stunnel.services.service') %}
{% for service in helpers.toList('OPNsense.Stunnel.services.service') %}
{% if service.enabled|default('0') == '1' %}
; **************************************************************************
; * {{ service.description }}
; **************************************************************************
[{{service['@uuid']}}]
accept = {% if service.accept_address %}{{service.accept_address}}:{% endif %}{{service.accept_port}}
connect = {% if service.connect_address.find(":") > -1 %}[{{service.connect_address}}]{% else %}{{service.connect_address}}{% endif %}:{{service.connect_port}}
cert = /usr/local/etc/stunnel/certs/{{service['@uuid']}}.crt
{% if service.cacert|default('') != '' %}
CAfile = /usr/local/etc/stunnel/certs/{{service['@uuid']}}.ca
requireCert = yes
verifyChain = yes
{% if service.enableCRL|default('0') == '1' %}
CRLpath = /certs/
{% endif %}
{% endif %}
{% set ciphers =[] %}
{% set ciphersuites =[] %}
{% for cipher in service.ciphers.split(',') %}
{% if cipher.startswith('TLS') %}
{% do ciphersuites.append(cipher) %}
{% else %}
{% do ciphers.append(cipher) %}
{% endif %}
{% endfor %}
ciphers = {{ ciphers|join(':') }}
ciphersuites = {{ ciphersuites|join(':') }}
sslVersionMin=TLSv1.2
sslVersionMax={% if ciphersuites %}TLSv1.3{% else %}TLSv1.2{% endif %}
{% endif %}
{% endfor %}
{% endif %}
@@ -0,0 +1,6 @@
###################################################################
# Local syslog-ng configuration filter definition [stunnel].
###################################################################
filter f_local_stunnel {
program("stunnel");
};