mirror of
https://github.com/lucaspalomodevelop/core.git
synced 2026-03-15 09:04:39 +00:00
VPN: OpenVPN: Connection Status - refactor to MVC closes https://github.com/opnsense/core/issues/6382
o rename virtual_addr --> virtual_address in status call out o add new endpoints to search connections and routes, kill sessions and service control o remove old status page status_openvpn.php and change ACL and Menu registration o offer two tab view on sessions / routes o service controls (restart/start/stop) are shown for non client based records (p2p and client mode) or when no clients are connected.
This commit is contained in:
parent
2d31af2a5e
commit
b9a1633a18
5
plist
5
plist
@ -384,7 +384,9 @@
|
||||
/usr/local/opnsense/mvc/app/controllers/OPNsense/Monit/forms/services.xml
|
||||
/usr/local/opnsense/mvc/app/controllers/OPNsense/Monit/forms/tests.xml
|
||||
/usr/local/opnsense/mvc/app/controllers/OPNsense/OpenVPN/Api/ExportController.php
|
||||
/usr/local/opnsense/mvc/app/controllers/OPNsense/OpenVPN/Api/ServiceController.php
|
||||
/usr/local/opnsense/mvc/app/controllers/OPNsense/OpenVPN/ExportController.php
|
||||
/usr/local/opnsense/mvc/app/controllers/OPNsense/OpenVPN/StatusController.php
|
||||
/usr/local/opnsense/mvc/app/controllers/OPNsense/OpenVPN/forms/export_options.xml
|
||||
/usr/local/opnsense/mvc/app/controllers/OPNsense/Proxy/Api/ServiceController.php
|
||||
/usr/local/opnsense/mvc/app/controllers/OPNsense/Proxy/Api/SettingsController.php
|
||||
@ -720,6 +722,7 @@
|
||||
/usr/local/opnsense/mvc/app/views/OPNsense/Monit/index.volt
|
||||
/usr/local/opnsense/mvc/app/views/OPNsense/Monit/status.volt
|
||||
/usr/local/opnsense/mvc/app/views/OPNsense/OpenVPN/export.volt
|
||||
/usr/local/opnsense/mvc/app/views/OPNsense/OpenVPN/status.volt
|
||||
/usr/local/opnsense/mvc/app/views/OPNsense/Proxy/index.volt
|
||||
/usr/local/opnsense/mvc/app/views/OPNsense/Routes/index.volt
|
||||
/usr/local/opnsense/mvc/app/views/OPNsense/Syslog/index.volt
|
||||
@ -935,6 +938,7 @@
|
||||
/usr/local/opnsense/scripts/openssh/ssh_query.py
|
||||
/usr/local/opnsense/scripts/openvpn/client_connect.php
|
||||
/usr/local/opnsense/scripts/openvpn/client_disconnect.sh
|
||||
/usr/local/opnsense/scripts/openvpn/kill_session.py
|
||||
/usr/local/opnsense/scripts/openvpn/ovpn_event.py
|
||||
/usr/local/opnsense/scripts/openvpn/ovpn_status.py
|
||||
/usr/local/opnsense/scripts/openvpn/tls_verify.php
|
||||
@ -1969,7 +1973,6 @@
|
||||
/usr/local/www/status_habackup.php
|
||||
/usr/local/www/status_interfaces.php
|
||||
/usr/local/www/status_ntpd.php
|
||||
/usr/local/www/status_openvpn.php
|
||||
/usr/local/www/status_wireless.php
|
||||
/usr/local/www/system_advanced_admin.php
|
||||
/usr/local/www/system_advanced_firewall.php
|
||||
|
||||
@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (C) 2023 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\OpenVPN\Api;
|
||||
|
||||
use OPNsense\Base\ApiControllerBase;
|
||||
use OPNsense\Core\Config;
|
||||
use OPNsense\Core\Backend;
|
||||
|
||||
/**
|
||||
* Class ServiceController
|
||||
* @package OPNsense\OpenVPN
|
||||
*/
|
||||
class ServiceController extends ApiControllerBase
|
||||
{
|
||||
private function getConfigs($role)
|
||||
{
|
||||
$config = Config::getInstance()->object();
|
||||
$config_payload = [];
|
||||
$cnf_section = 'openvpn-' . $role;
|
||||
if (!empty($config->openvpn->$cnf_section)) {
|
||||
foreach ($config->openvpn->$cnf_section as $cnf) {
|
||||
if (!empty((string)$cnf->vpnid)) {
|
||||
$config_payload[(string)$cnf->vpnid] = $cnf;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $config_payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search sessions
|
||||
* @return array
|
||||
*/
|
||||
public function searchSessionsAction()
|
||||
{
|
||||
$this->sessionClose();
|
||||
$data = json_decode((new Backend())->configdRun('openvpn connections client,server') ?? '', true) ?? [];
|
||||
$records = [];
|
||||
$roles = ['client', 'server'];
|
||||
if ($this->request->has('type') && is_array($this->request->get('type'))) {
|
||||
$roles = array_intersect($this->request->get('type'), $roles);
|
||||
}
|
||||
foreach ($roles as $role) {
|
||||
$config_payload = $this->getConfigs($role);
|
||||
$vpnids = [];
|
||||
if (!empty($data[$role])) {
|
||||
foreach ($data[$role] as $idx => $stats) {
|
||||
$vpnids[] = $idx;
|
||||
$stats['type'] = $role;
|
||||
$stats['id'] = $idx;
|
||||
$stats['description'] = '';
|
||||
$stats['connected_since'] = null;
|
||||
if (!empty($stats['timestamp'])) {
|
||||
$stats['connected_since'] = date('Y-m-d H:i:s', $stats['timestamp']);
|
||||
}
|
||||
if (!empty($config_payload[$idx])) {
|
||||
$stats['description'] = (string)$config_payload[$idx]->description ?? '';
|
||||
}
|
||||
if (!empty($stats['client_list'])) {
|
||||
foreach ($stats['client_list'] as $client) {
|
||||
$tmp = array_merge($stats, $client);
|
||||
$tmp['id'] .= '_' . $client['real_address'];
|
||||
$tmp['is_client'] = true;
|
||||
$records[] = $tmp;
|
||||
}
|
||||
} else {
|
||||
$records[] = $stats;
|
||||
}
|
||||
}
|
||||
}
|
||||
// add non running enabled servers
|
||||
foreach ($config_payload as $idx => $cnf) {
|
||||
if (!in_array($idx, $vpnids) && empty((string)$cnf->disable)) {
|
||||
$records[] = [
|
||||
'id' => $idx,
|
||||
'service_id' => "openvpn/".$idx,
|
||||
'type' => $role,
|
||||
'description' => (string)$cnf->description ?? '',
|
||||
'connected_since' => null,
|
||||
'status' => null
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->searchRecordsetBase($records);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search routes
|
||||
* @return array
|
||||
*/
|
||||
public function searchRoutesAction()
|
||||
{
|
||||
$records = [];
|
||||
$data = json_decode((new Backend())->configdRun('openvpn connections client,server') ?? '', true) ?? [];
|
||||
$records = [];
|
||||
$roles = ['client', 'server'];
|
||||
if ($this->request->has('type') && is_array($this->request->get('type'))) {
|
||||
$roles = array_intersect($this->request->get('type'), $roles);
|
||||
}
|
||||
foreach ($roles as $role) {
|
||||
if (!empty($data[$role])) {
|
||||
$config_payload = $this->getConfigs($role);
|
||||
foreach ($data[$role] as $idx => $payload) {
|
||||
if (!empty($payload['routing_table'])) {
|
||||
foreach ($payload['routing_table'] as $route_entry) {
|
||||
$route_entry['type'] = $role;
|
||||
$route_entry['id'] = $idx;
|
||||
$route_entry['description'] = '';
|
||||
if (!empty($config_payload[$idx])) {
|
||||
$route_entry['description'] = (string)$config_payload[$idx]->description ?? '';
|
||||
}
|
||||
$records[] = $route_entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->searchRecordsetBase($records);
|
||||
}
|
||||
|
||||
/**
|
||||
* kill session by source ip:port or common name
|
||||
* @return array
|
||||
*/
|
||||
public function killSessionAction()
|
||||
{
|
||||
if (!$this->request->isPost()) {
|
||||
return ['result' => 'failed'];
|
||||
}
|
||||
$this->sessionClose();
|
||||
$server_id = $this->request->get('server_id', null);
|
||||
$session_id = $this->request->get('session_id', null);
|
||||
if ($server_id != null && $session_id != null) {
|
||||
$data = json_decode((new Backend())->configdpRun('openvpn kill',[$server_id, $session_id]) ?? '', true);
|
||||
if (!empty($data)) {
|
||||
return $data;
|
||||
}
|
||||
return ['result' => 'failed'];
|
||||
} else{
|
||||
return ['status' => 'invalid'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id server/client id to start
|
||||
* @return array
|
||||
*/
|
||||
public function startServiceAction($id=null)
|
||||
{
|
||||
if (!$this->request->isPost()) {
|
||||
return ['result' => 'failed'];
|
||||
}
|
||||
|
||||
$this->sessionClose();
|
||||
|
||||
(new Backend())-> configdpRun('service start', ['openvpn', $id]);
|
||||
|
||||
return ['result' => 'ok'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id server/client id to stop
|
||||
* @return array
|
||||
*/
|
||||
public function stopServiceAction($id=null)
|
||||
{
|
||||
if (!$this->request->isPost()) {
|
||||
return ['result' => 'failed'];
|
||||
}
|
||||
|
||||
$this->sessionClose();
|
||||
|
||||
(new Backend())-> configdpRun('service stop', ['openvpn', $id]);
|
||||
|
||||
return ['result' => 'ok'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id server/client id to restart
|
||||
* @return array
|
||||
*/
|
||||
public function restartServiceAction($id=null)
|
||||
{
|
||||
if (!$this->request->isPost()) {
|
||||
return ['result' => 'failed'];
|
||||
}
|
||||
|
||||
$this->sessionClose();
|
||||
|
||||
(new Backend())-> configdpRun('service restart', ['openvpn', $id]);
|
||||
|
||||
return ['result' => 'ok'];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (C) 2023 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\OpenVPN;
|
||||
|
||||
use OPNsense\Base\IndexController as BaseIndexController;
|
||||
|
||||
/**
|
||||
* Class StatusController
|
||||
* @package OPNsense\OpenVPN
|
||||
*/
|
||||
class StatusController extends BaseIndexController
|
||||
{
|
||||
/**
|
||||
* default index page
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function indexAction()
|
||||
{
|
||||
$this->view->pick('OPNsense/OpenVPN/status');
|
||||
}
|
||||
}
|
||||
@ -515,7 +515,8 @@
|
||||
<page-status-openvpn>
|
||||
<name>Status: OpenVPN</name>
|
||||
<patterns>
|
||||
<pattern>status_openvpn.php*</pattern>
|
||||
<pattern>ui/openvpn/status</pattern>
|
||||
<pattern>api/openvpn/service/*</pattern>
|
||||
</patterns>
|
||||
</page-status-openvpn>
|
||||
<page-status-services>
|
||||
|
||||
@ -217,7 +217,7 @@
|
||||
<ClientExport order="40" VisibleName="Client Export" url="/ui/openvpn/export">
|
||||
<Edit url="/ui/openvpn/export?*" visibility="hidden"/>
|
||||
</ClientExport>
|
||||
<Status order="60" VisibleName="Connection Status" url="/status_openvpn.php"/>
|
||||
<Status order="60" VisibleName="Connection Status" url="/ui/openvpn/status"/>
|
||||
<LogFile order="70" VisibleName="Log File" url="/ui/diagnostics/log/core/openvpn"/>
|
||||
</OpenVPN>
|
||||
</VPN>
|
||||
|
||||
162
src/opnsense/mvc/app/views/OPNsense/OpenVPN/status.volt
Normal file
162
src/opnsense/mvc/app/views/OPNsense/OpenVPN/status.volt
Normal file
@ -0,0 +1,162 @@
|
||||
{#
|
||||
|
||||
OPNsense® is Copyright © 2023 by Deciso B.V.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#}
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
$( document ).ready(function () {
|
||||
let grid_sessions = $("#grid-sessions").UIBootgrid({
|
||||
search:'/api/openvpn/service/search_sessions',
|
||||
options:{
|
||||
selection: false,
|
||||
formatters:{
|
||||
commands: function (column, row) {
|
||||
if (row.is_client) {
|
||||
return '<button type="button" class="btn btn-xs btn-default ovpn-command command-kill" data-toggle="tooltip" title="{{ lang._('Kill') }}" data-common_name="'+row.common_name+'" data-row-id="' + row.id + '"><span class="fa fa-times fa-fw"></span></button>';
|
||||
} else if (row.status === null) {
|
||||
return '<button type="button" class="btn btn-xs btn-default ovpn-command command-start" data-toggle="tooltip" title="{{ lang._('Start') }}" data-row-id="' + row.id + '"><span class="fa fa-play fa-fw"></span></button>';
|
||||
} else {
|
||||
return '<button type="button" class="btn btn-xs btn-default ovpn-command command-restart" data-toggle="tooltip" title="{{ lang._('Restart') }}" data-row-id="' + row.id + '"><span class="fa fa-repeat fa-fw"></span></button>' +
|
||||
'<button type="button" class="btn btn-xs btn-default ovpn-command command-stop" data-toggle="tooltip" title="{{ lang._('Stop') }}" data-row-id="' + row.id + '"><span class="fa fa-stop fa-fw"></span></button>';
|
||||
}
|
||||
}
|
||||
},
|
||||
requestHandler: function(request){
|
||||
if ( $('#type_filter').val().length > 0) {
|
||||
request['type'] = $('#type_filter').val();
|
||||
}
|
||||
return request;
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
$("#grid-routes").UIBootgrid({
|
||||
search:'/api/openvpn/service/search_routes',
|
||||
options:{
|
||||
selection: false
|
||||
}
|
||||
});
|
||||
|
||||
grid_sessions.on('loaded.rs.jquery.bootgrid', function () {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
$(".ovpn-command").click(function(){
|
||||
let this_cmd = $(this);
|
||||
if (this_cmd.hasClass('command-kill')) {
|
||||
let tmp = this_cmd.data('row-id').split('_');
|
||||
if (tmp.length == 2) {
|
||||
let params = {server_id: tmp[0], session_id: tmp[1]};
|
||||
ajaxCall('/api/openvpn/service/kill_session/', params, function(data, status){
|
||||
if (data && data.status === 'not_found') {
|
||||
// kill by common name
|
||||
params.session_id = this_cmd.data('common_name');
|
||||
ajaxCall('/api/openvpn/service/kill_session/', params, function(data, status){
|
||||
$('#grid-sessions').bootgrid('reload');
|
||||
});
|
||||
} else{
|
||||
$('#grid-sessions').bootgrid('reload');
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (this_cmd.hasClass('command-start')) {
|
||||
ajaxCall('/api/openvpn/service/start_service/' + this_cmd.data('row-id'), {}, function(data, status){
|
||||
$('#grid-sessions').bootgrid('reload');
|
||||
});
|
||||
} else if (this_cmd.hasClass('command-stop')) {
|
||||
ajaxCall('/api/openvpn/service/stop_service/' + this_cmd.data('row-id'), {}, function(data, status){
|
||||
$('#grid-sessions').bootgrid('reload');
|
||||
});
|
||||
} else if (this_cmd.hasClass('command-restart')) {
|
||||
ajaxCall('/api/openvpn/service/restart_service/' + this_cmd.data('row-id'), {}, function(data, status){
|
||||
$('#grid-sessions').bootgrid('reload');
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#type_filter").change(function(){
|
||||
$('#grid-sessions').bootgrid('reload');
|
||||
});
|
||||
|
||||
$("#type_filter_container").detach().prependTo('#grid-sessions-header > .row > .actionBar > .actions');
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
|
||||
<li class="active"><a data-toggle="tab" href="#sessions">{{ lang._('Sessions') }}</a></li>
|
||||
<li><a data-toggle="tab" href="#routes">{{ lang._('Routes') }}</a></li>
|
||||
</ul>
|
||||
<div class="tab-content content-box">
|
||||
<div id="sessions" class="tab-pane fade in active">
|
||||
<div class="hidden">
|
||||
<!-- filter per type container -->
|
||||
<div id="type_filter_container" class="btn-group">
|
||||
<select id="type_filter" data-title="{{ lang._('Filter type') }}" class="selectpicker" data-live-search="true" multiple="multiple" data-width="200px">
|
||||
<option value="server">{{ lang._('Server') }}</option>
|
||||
<option value="client">{{ lang._('Client') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<table id="grid-sessions" class="table table-condensed table-hover table-striped table-responsive">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="id" data-type="string" data-sortable="false" data-identifier="true" data-visible="false">{{ lang._('ID') }}</th>
|
||||
<th data-column-id="type" data-type="string">{{ lang._('Type') }}</th>
|
||||
<th data-column-id="description" data-type="string">{{ lang._('Description') }}</th>
|
||||
<th data-column-id="common_name" data-type="string">{{ lang._('Common Name') }}</th>
|
||||
<th data-column-id="real_address" data-type="string">{{ lang._('Real Address') }}</th>
|
||||
<th data-column-id="virtual_address" data-type="string">{{ lang._('Virtual Address') }}</th>
|
||||
<th data-column-id="connected_since" data-type="string">{{ lang._('Connected Since') }}</th>
|
||||
<th data-column-id="bytes_sent" data-type="string">{{ lang._('Bytes Sent') }}</th>
|
||||
<th data-column-id="bytes_received" data-type="string">{{ lang._('Bytes Received') }}</th>
|
||||
<th data-column-id="status" data-type="string">{{ lang._('Status') }}</th>
|
||||
<th data-column-id="commands" data-width="5em" data-formatter="commands" data-sortable="false"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div id="routes" class="tab-pane fade in">
|
||||
<table id="grid-routes" class="table table-condensed table-hover table-striped table-responsive">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="id" data-type="string" data-sortable="false" data-visible="false">{{ lang._('ID') }}</th>
|
||||
<th data-column-id="type" data-type="string">{{ lang._('Type') }}</th>
|
||||
<th data-column-id="description" data-type="string">{{ lang._('Description') }}</th>
|
||||
<th data-column-id="common_name" data-type="string">{{ lang._('Common Name') }}</th>
|
||||
<th data-column-id="real_address" data-type="string">{{ lang._('Real Address') }}</th>
|
||||
<th data-column-id="virtual_address" data-type="string">{{ lang._('Target Network') }}</th>
|
||||
<th data-column-id="last_ref" data-type="string">{{ lang._('Last referenced') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
82
src/opnsense/scripts/openvpn/kill_session.py
Executable file
82
src/opnsense/scripts/openvpn/kill_session.py
Executable file
@ -0,0 +1,82 @@
|
||||
#!/usr/local/bin/python3
|
||||
|
||||
"""
|
||||
Copyright (c) 2023 Ad Schellevis <ad@opnsense.org>
|
||||
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.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import socket
|
||||
import re
|
||||
import os
|
||||
import ujson
|
||||
socket.setdefaulttimeout(5)
|
||||
|
||||
|
||||
|
||||
def ovpn_cmd(filename, cmd):
|
||||
try:
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sock.connect(filename)
|
||||
except socket.error:
|
||||
return None
|
||||
|
||||
sock.send(('%s\n'%cmd).encode())
|
||||
buffer = ''
|
||||
while True:
|
||||
try:
|
||||
buffer += sock.recv(65536).decode()
|
||||
except socket.timeout:
|
||||
break
|
||||
eob = buffer[-200:]
|
||||
if eob.find('END') > -1 or eob.find('ERROR') > -1 or eob.find('SUCCESS') > -1:
|
||||
break
|
||||
sock.close()
|
||||
return buffer
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('server_id', help='server/client id (where to find socket)', type=int)
|
||||
parser.add_argument('session_id', help='session id (address+port) or common name')
|
||||
args = parser.parse_args()
|
||||
socket_name = None
|
||||
for filename in glob.glob("/var/etc/openvpn/*.sock"):
|
||||
basename = os.path.basename(filename)
|
||||
if basename in ['client%d.sock'%args.server_id, 'server%d.sock'%args.server_id]:
|
||||
socket_name = filename
|
||||
break
|
||||
if socket_name:
|
||||
res = ovpn_cmd(socket_name, 'kill %s\n' % args.session_id)
|
||||
if res.find('SUCCESS:') >= 0:
|
||||
clients = 0
|
||||
for tmp in res.strip().split('\n')[-1].split():
|
||||
if tmp.isdigit():
|
||||
clients = int(tmp)
|
||||
print(ujson.encode({'status': 'killed', 'clients': clients}))
|
||||
else:
|
||||
print(ujson.encode({'status': 'not_found'}))
|
||||
else:
|
||||
print(ujson.encode({'status': 'server_not_found'}))
|
||||
@ -99,7 +99,7 @@ def ovpn_state(filename):
|
||||
if len(tmp) > 2 and tmp[0].isdigit():
|
||||
response['timestamp'] = int(tmp[0])
|
||||
response['status'] = tmp[1].lower()
|
||||
response['virtual_addr'] = tmp[3] if len(tmp) > 3 else ""
|
||||
response['virtual_address'] = tmp[3] if len(tmp) > 3 else ""
|
||||
response['remote_host'] = tmp[4] if len(tmp) > 4 else ""
|
||||
|
||||
return response
|
||||
|
||||
@ -3,3 +3,10 @@ command:/usr/local/opnsense/scripts/openvpn/ovpn_status.py
|
||||
parameters: --option %s
|
||||
type:script_output
|
||||
message:Query OpenVPN status (%s)
|
||||
|
||||
|
||||
[kill]
|
||||
command:/usr/local/opnsense/scripts/openvpn/kill_session.py
|
||||
parameters: %s %s
|
||||
type:script_output
|
||||
message:Kill OpenVPN session %s - %s
|
||||
|
||||
@ -1,304 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (C) 2014-2023 Deciso B.V.
|
||||
* Copyright (C) 2019 Franco Fichtner <franco@opnsense.org>
|
||||
* Copyright (C) 2010 Jim Pingle <jimp@pfsense.org>
|
||||
* Copyright (C) 2008 Shrew Soft Inc. <mgrooms@shrew.net>
|
||||
* Copyright (C) 2005 Scott Ullrich <sullrich@gmail.com>
|
||||
* Copyright (C) 2005 Colin Smith <ethethlay@gmail.com>
|
||||
* 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("guiconfig.inc");
|
||||
require_once("interfaces.inc");
|
||||
require_once("plugins.inc.d/openvpn.inc");
|
||||
|
||||
function kill_client($port, $client = null)
|
||||
{
|
||||
$tcpsrv = "unix:///var/etc/openvpn/{$port}.sock";
|
||||
$errval = '';
|
||||
$errstr = '';
|
||||
|
||||
/* open a tcp connection to the management port of each server */
|
||||
$fp = @stream_socket_client($tcpsrv, $errval, $errstr, 1);
|
||||
$killed = -1;
|
||||
if ($fp) {
|
||||
stream_set_timeout($fp, 1);
|
||||
fputs($fp, "kill {$client}\n");
|
||||
while (!feof($fp)) {
|
||||
$line = fgets($fp, 1024);
|
||||
|
||||
$info = stream_get_meta_data($fp);
|
||||
if ($info['timed_out']) {
|
||||
break;
|
||||
}
|
||||
/* parse header list line */
|
||||
if (strpos($line, "INFO:") !== false) {
|
||||
continue;
|
||||
}
|
||||
if (strpos($line, "SUCCESS") !== false) {
|
||||
$killed = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
fclose($fp);
|
||||
}
|
||||
return $killed;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
$vpnid = 0;
|
||||
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (isset($_POST['action']) && $_POST['action'] == 'kill') {
|
||||
$port = $_POST['port'];
|
||||
$remipp = $_POST['remipp'];
|
||||
$common_name = $_POST['common_name'];
|
||||
if (!empty($port) && !empty($remipp)) {
|
||||
$retval = kill_client($port, $remipp);
|
||||
if ($retval == -1 && !empty($common_name)) {
|
||||
// kill by common name when the address couldn't be killed
|
||||
$retval = kill_client($port, $common_name);
|
||||
echo html_safe("|{$port}|{$common_name}|{$retval}|");
|
||||
} else {
|
||||
echo html_safe("|{$port}|{$remipp}|{$retval}|");
|
||||
}
|
||||
} else {
|
||||
echo gettext("invalid input");
|
||||
}
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
$openvpn_status = json_decode(configd_run('openvpn connections client,server'), true) ?? [];
|
||||
$openvpn_cfg = openvpn_config();
|
||||
legacy_html_escape_form_data($openvpn_status);
|
||||
legacy_html_escape_form_data($openvpn_cfg);
|
||||
|
||||
include("head.inc"); ?>
|
||||
|
||||
|
||||
<body>
|
||||
<?php include("fbegin.inc"); ?>
|
||||
|
||||
<script>
|
||||
//<![CDATA[
|
||||
$(document).ready(function () {
|
||||
// link kill buttons
|
||||
$(".act_kill_client").click(function (event) {
|
||||
event.preventDefault();
|
||||
var port = $(this).data("client-port");
|
||||
var ip = $(this).data("client-ip");
|
||||
let common_name = $(this).data("common_name");
|
||||
$.post(window.location, {action: 'kill', port: port, remipp: ip, common_name:common_name}, function (data) {
|
||||
location.reload();
|
||||
});
|
||||
});
|
||||
// link show/hide routes
|
||||
$(".act_show_routes").click(function () {
|
||||
$("*[data-for='" + $(this).attr('id') + "']").toggle();
|
||||
});
|
||||
|
||||
// minimize all buttons, some of the buttons come from the shared service
|
||||
// functions, which outputs large buttons.
|
||||
$(".btn").each(function () {
|
||||
$(this).addClass("btn-xs");
|
||||
});
|
||||
|
||||
});
|
||||
//]]>
|
||||
</script>
|
||||
<section class="page-content-main">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<section class="col-xs-12">
|
||||
<!-- XXX unused? <form method="get" name="iform">-->
|
||||
<?php foreach ($openvpn_cfg['openvpn-server'] as $i => $server): ?>
|
||||
<div class="tab-content content-box __mb">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="7">
|
||||
<strong><?= $server['name'] ?> <?= gettext('Client connections') ?></strong>
|
||||
<div class="pull-right">
|
||||
<?php $ssvc = service_by_name('openvpn', array('id' => $server['vpnid'])); ?>
|
||||
<?= service_control_icon($ssvc, true); ?>
|
||||
<?= service_control_links($ssvc, true); ?>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong><?= gettext('Common Name') ?></strong></td>
|
||||
<td><strong><?= gettext('Real Address') ?></strong></td>
|
||||
<td><strong><?= gettext('Virtual Address') ?></strong></td>
|
||||
<td><strong><?= gettext('Connected Since') ?></strong></td>
|
||||
<td><strong><?= gettext('Bytes Sent') ?></strong></td>
|
||||
<td><strong><?= gettext('Bytes Received') ?></strong></td>
|
||||
<td><strong><?= gettext('Status') ?></strong></td>
|
||||
</tr>
|
||||
<?php if (empty($openvpn_status['server'][$server['vpnid']]) || empty($openvpn_status['server'][$server['vpnid']]['client_list'])): ?>
|
||||
<?php if (!empty($openvpn_status['server'][$server['vpnid']]) && isset($openvpn_status['server'][$server['vpnid']]['write_bytes'])):?>
|
||||
<?php $conn = $openvpn_status['server'][$server['vpnid']];?>
|
||||
<tr>
|
||||
<td><?= $conn['common_name'] ?? '' ?></td>
|
||||
<td><?= $conn['real_address'] ?? '' ?></td>
|
||||
<td><?= $conn['virtual_addr'] ?? '' ?></td>
|
||||
<td><?= date('Y-m-d H:i:s', $conn['timestamp']) ?></td>
|
||||
<td><?= format_bytes($conn['bytes_sent']) ?></td>
|
||||
<td><?= format_bytes($conn['bytes_received']) ?></td>
|
||||
<td><?= $conn['status'];?> </td>
|
||||
</tr>
|
||||
<?php else:?>
|
||||
<tr>
|
||||
<td colspan="7">
|
||||
<?= gettext('No OpenVPN clients are connected to this instance.') ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif ?>
|
||||
<?php else: ?>
|
||||
<?php foreach ($openvpn_status['server'][$server['vpnid']]['client_list'] as $conn): ?>
|
||||
<tr>
|
||||
<td><?= $conn['common_name'] ?></td>
|
||||
<td><?= $conn['real_address'] ?></td>
|
||||
<td><?= $conn['virtual_address'] ?></td>
|
||||
<td><?= $conn['connected_since'] ?></td>
|
||||
<td><?= format_bytes($conn['bytes_sent']) ?></td>
|
||||
<td><?= format_bytes($conn['bytes_received']) ?></td>
|
||||
<td>
|
||||
<?php if ($conn['common_name'] != '[error]'): ?>
|
||||
<button data-client-port="server<?= $server['vpnid']; ?>"
|
||||
data-client-ip="<?= $conn['real_address']; ?>"
|
||||
data-common_name="<?= $conn['common_name']; ?>"
|
||||
title="<?= gettext("Kill client connection from") . " " . $conn['real_address']; ?>"
|
||||
class="act_kill_client btn btn-default">
|
||||
<i class="fa fa-times fa-fw"></i>
|
||||
</button>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
<?php endif ?>
|
||||
<?php if (!empty($openvpn_status['server'][$server['vpnid']]) && !empty($openvpn_status['server'][$server['vpnid']]['routing_table'])): ?>
|
||||
<tr>
|
||||
<td colspan="7">
|
||||
<span style="cursor:pointer;" class="act_show_routes" id="showroutes_<?= $i ?>">
|
||||
<i class="fa fa-chevron-down fa-fw"></i>
|
||||
<strong><?= $server['name'] ?> <?= gettext('Routing Table') ?></strong>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="display:none;" data-for="showroutes_<?= $i ?>">
|
||||
<td><strong><?= gettext('Common Name') ?></strong></td>
|
||||
<td><strong><?= gettext('Real Address') ?></strong></td>
|
||||
<td><strong><?= gettext('Target Network') ?></strong></td>
|
||||
<td><strong><?= gettext('Last Used') ?></strong></td>
|
||||
<td colspan="3">
|
||||
</tr>
|
||||
<?php foreach ($openvpn_status['server'][$server['vpnid']]['routing_table'] as $conn): ?>
|
||||
<tr style="display:none;" data-for="showroutes_<?= $i ?>" id="<?= html_safe("r:{$server['mgmt']}:{$conn['remote_host']}") ?>">
|
||||
<td><?= $conn['common_name'] ?></td>
|
||||
<td><?= $conn['real_address'] ?></td>
|
||||
<td><?= $conn['virtual_address'] ?></td>
|
||||
<td><?= $conn['last_ref'] ?></td>
|
||||
<td colspan="3">
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
<tr style="display:none;" data-for="showroutes_<?= $i ?>">
|
||||
<td colspan="7"><?= gettext("An IP address followed by C indicates a host currently connected through the VPN.") ?></td>
|
||||
</tr>
|
||||
</tr>
|
||||
<?php endif ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach ?>
|
||||
|
||||
<?php if (!empty($openvpn_cfg['openvpn-client'])): ?>
|
||||
<div class="tab-content content-box __mb">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="8"><strong><?= gettext('Client Instance Statistics') ?><strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong><?= gettext('Name') ?></strong></td>
|
||||
<td><strong><?= gettext('Remote Host') ?></strong></td>
|
||||
<td><strong><?= gettext('Virtual Addr') ?></strong></td>
|
||||
<td><strong><?= gettext('Connected Since') ?></strong></td>
|
||||
<td><strong><?= gettext('Bytes Sent') ?></strong></td>
|
||||
<td><strong><?= gettext('Bytes Received') ?></strong></td>
|
||||
<td><strong><?= gettext('Status') ?></strong></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<?php foreach ($openvpn_cfg['openvpn-client'] as $client): ?>
|
||||
<?php $conn = $openvpn_status['client'][$client['vpnid']] ?? [];?>
|
||||
<tr id="<?= html_safe("r:{$client['port']}:{$client['vpnid']}") ?>">
|
||||
<td><?= $client['name'] ?></td>
|
||||
<td><?= $conn['remote_host'] ?? ''?></td>
|
||||
<td><?= $conn['virtual_addr'] ?? '' ?></td>
|
||||
<td><?= !empty($conn) ? date('Y-m-d H:i:s', $conn['timestamp']) : '' ?></td>
|
||||
<td><?= format_bytes($conn['write_bytes'] ?? '0') ?></td>
|
||||
<td><?= format_bytes($conn['read_bytes'] ?? '0') ?></td>
|
||||
<td><?= !empty($conn) ? $conn['status'] : '' ?></td>
|
||||
<td>
|
||||
<div>
|
||||
<?php $ssvc = service_by_name('openvpn', array('id' => $client['vpnid'])); ?>
|
||||
<?= service_control_icon($ssvc, true); ?>
|
||||
<?= service_control_links($ssvc, true); ?>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<?php if (empty($openvpn_cfg['openvpn-server']) && empty($openvpn_cfg['openvpn-client'])): ?>
|
||||
<div class="tab-content content-box __mb">
|
||||
<div class="table-responsive">
|
||||
<table class="table-responsive table table-striped">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="8"><strong><?= gettext('OpenVPN Status') ?></strong></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="8"><?= gettext('No OpenVPN instance defined') ?></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<!--</form>-->
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<?php
|
||||
|
||||
include 'foot.inc';
|
||||
@ -89,7 +89,7 @@ foreach ($openvpn_cfg as $section => &$ovpncfg) {
|
||||
elseif (!empty($server['timestamp'])):?>
|
||||
<tr>
|
||||
<td><?=date('Y-m-d H:i:s', $server['timestamp']);?></td>
|
||||
<td><?=$server['remote_host'];?><br/><?=$server['virtual_addr'];?></td>
|
||||
<td><?=$server['remote_host'];?><br/><?=$server['virtual_address'];?></td>
|
||||
<td>
|
||||
<span class='fa fa-exchange fa-fw <?=$server['status'] == "CONNECTED" ? "text-success" : "text-danger" ;?>'></span>
|
||||
</td>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user