furlongm / openvpn-monitor

openvpn-monitor is a web based OpenVPN monitor, that shows current connection information, such as users, location and data transferred.
http://openvpn-monitor.openbytes.ie
GNU General Public License v3.0
963 stars 293 forks source link

Feature request - Disconnect clients #17

Closed kidmock closed 7 years ago

kidmock commented 7 years ago

Hello I think your tool is awesome. However, I think it would be even better if you could disconnect active clients.

I managed to add this feature in my own hacky way by creating a button to a php script. But it would be great if this was native to your python program? What do you think?

Here are the changes I made

--- openvpn-monitor/openvpn-monitor.py  2016-10-06 18:18:11.476953625 -0400
+++ openvpn-monitor.py  2016-10-06 18:38:04.430057188 -0400
@@ -444,7 +444,7 @@
         output('<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">')
         output('<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">')
         output('<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>')
-        output('<body>')
+        output('</head><body>')

         output('<nav class="navbar navbar-inverse">')
         output('<div class="container-fluid">')
@@ -489,7 +489,7 @@

         server_headers = ['Username / Hostname', 'VPN IP Address',
                           'Remote IP Address', 'Port', 'Location', 'Bytes In',
-                          'Bytes Out', 'Connected Since', 'Last Ping', 'Time Online']
+                          'Bytes Out', 'Connected Since', 'Last Ping', 'Time Online', 'Action']
         client_headers = ['Tun-Tap-Read', 'Tun-Tap-Write', 'TCP-UDP-Read',
                           'TCP-UDP-Write', 'Auth-Read']

@@ -609,6 +609,7 @@
         else:
             output('<td>ERROR</td>')
         output('<td>{0!s}</td>'.format(total_time))
+        output('<td><a href="/cgi-bin/kill.php?vpnclient={0!s}:{1!s}"><button type="button" >Disconnect</button></a> </td>'.format(session['remote_ip'], session['port']))

     def print_session_table(self, vpn_mode, sessions):
         for key, session in list(sessions.items()):

Here's my crappy PHP

<?php

$vpnclient=$_GET[vpnclient];

$fp = fsockopen("localhost", 5555, $errno, $errstr, 30);

if (!$fp) {
    echo "$errstr ($errno)<br />\n";
} else {
    $out = "kill $vpnclient\r\n";
    $out .= "quit\r\n";
    fwrite($fp, $out);
    while (!feof($fp)) {
        echo fgets($fp, 128);
    }
    fclose($fp);
}

        echo "<html> <head> <meta http-equiv='refresh' content='3;/monitor/' /> </head> <body>";
        echo "<h1>$vpnclient disconnected</h1>";
        echo "</body></html>";
?>

And a screenshot of how it looks

screenshot from 2016-10-06 18 29 43

furlongm commented 7 years ago

I would like to add this functionality, but it looks like the source port will be removed from the management interface. See https://community.openvpn.net/openvpn/ticket/664 for more details.

kidmock commented 7 years ago

Thanks for for trying. That really sucks. I wonder if they will come up with an alternative way to find unique connections. I guess I'll fork and use my crappy work around since I don't use ipv6

furlongm commented 7 years ago

Currently the port is there for ipv4 addresses in openvpn < 2.4, but according to that bug report, the port will disappear in 2.4, for both ipv4 and ipv6.

kidmock commented 7 years ago

Yeah, I get it If you enable this feature with 2.4 or ipv6 You run the risk of nuking all users. :( Bummer. I'm not mad

furlongm commented 7 years ago

Can you see if 9c5969f60872b336f4055561330380b6cea89f62 gives you the functionality you are looking for?

kidmock commented 7 years ago

Well.... It looks good but doesn't work :)

Additionally, semantic_version isn't available from epel on RHEL/CentOS 6.x. I had to install from pip.

pip install semantic_version

I'll do some debugging this evening and get you more feedback.

kidmock commented 7 years ago

Ok here's what I can observe. Sorry I'm a network dude not a developer. :)

When I hit the disconnect button, the Form Data is being set in the request:

vpn_id:VPN1
ip:***.***.***.***
port:35827

However this conditional is never true:

         if 'vpn_id' in kwargs:
furlongm commented 7 years ago

I've tried to reproduce this on CentOS 6 but it works fine for me. Are you using the latest commit? If not, can you try it?

If it still doesn't work, can you print kwargs to see what is in it?

kidmock commented 7 years ago

I threw in some additional logging but I'm not sure how to debug any further.

--- openvpn-monitor/openvpn-monitor.py  2017-01-05 11:21:13.196328443 +0000
+++ monitor/openvpn-monitor.py  2017-01-05 11:40:16.486459071 +0000
@@ -146,8 +146,10 @@

     def __init__(self, cfg, **kwargs):
         self.vpns = cfg.vpns
+        info('Initialize OpenvpnMgmtInterface class')

         if 'vpn_id' in kwargs:
+            info('vpn_id is in kwargs')
             self._socket_connect(self.vpns[kwargs['vpn_id']])
             if self.s:
                 version = self.send_command('version\n')

tried to post data with curl

curl -u username 'https://localhost/monitor/openvpn-monitor.py' --data 'vpn_id=VPN1&ip=***.***.***.***.&port=41630'

With the log results

[Thu Jan 05 11:54:28 2017] [error] [client ***.***.***.***] INFO: Using config file: ./openvpn-monitor.conf, referer: https://localhost/monitor/openvpn-monitor.py
[Thu Jan 05 11:54:28 2017] [error] [client ***.***.***.***] INFO: Initialize OpenvpnMgmtInterface class, referer: https://localhost/monitor/openvpn-monitor.py

Thoughts?

kidmock commented 7 years ago

Yes, I pulled down a fresh copy of the code just before I ran my test.

furlongm commented 7 years ago

Try info(kwargs)

kidmock commented 7 years ago
     def __init__(self, cfg, **kwargs):
         self.vpns = cfg.vpns
+        info('Initialize OpenvpnMgmtInterface class')
+        info(kwargs)
+        info('End')

         if 'vpn_id' in kwargs:
+            info('vpn_id is in kwargs')
             self._socket_connect(self.vpns[kwargs['vpn_id']])
             if self.s:
                 version = self.send_command('version\n')

yup Definitely not seeing the post data. hmmm

[Thu Jan 05 12:36:46 2017] [error] [client **.***.***.***] INFO: Initialize OpenvpnMgmtInterface class, referer: https://localhost/monitor
[Thu Jan 05 12:36:46 2017] [error] [client **.***.***.***] INFO: {}, referer: https://localhost/monitor
[Thu Jan 05 12:36:46 2017] [error] [client **.***.***.***] INFO: End, referer: https://localhost/monitor

The only thing I can think of is I use LINOTP which is also a WSGI app. There might be some interference.

I'll have to really dig to see why the post data isn't being passed.

furlongm commented 7 years ago

Could you insert info(kwargs) into main() and render() ?

That way we can see at what point it gets lost.

furlongm commented 7 years ago

@kidmock any luck debugging this issue?

furlongm commented 7 years ago

I'm going to close this issue for now, as I can't reproduce. If you can reproduce, please re-open and add further details.