Add OpenVPNConnect Module (#11)

This commit is contained in:
Casey Erdmann 2018-03-06 01:10:38 -05:00 committed by Sebastian Kinne
parent abe6d3060c
commit 194526857f
4 changed files with 597 additions and 0 deletions

View File

@ -0,0 +1,214 @@
<?php
// Define the pineapple namespace
namespace pineapple;
// Upload Directory where we store vpn_configs
define('__UPLOAD__', "/root/vpn_config/");
/* Main module class for OpenVPNConnect */
class OpenVPNConnect extends Module{
// Set up our routes for our angular functions to call
public function route(){
switch ($this->request->action) {
case 'startVPN':
$this->startVPN();
break;
case 'stopVPN':
$this->stopVPN();
break;
case 'initializeModule':
$this->initializeModule();
break;
case 'handleDependencies':
$this->handleDependencies();
break;
case 'checkDependencies':
$this->checkDependencies();
break;
case 'uploadFile':
$this->uploadFile();
}
}
// Checks the dependencies using the pineapple API functions
private function checkDependencies(){
if($this->checkDependency('openvpn')){
$installLabel = 'success';
$installLabelText = 'Installed';
}else{
$installLabel = 'danger';
$installLabelText = 'Not Installed';
}
$this->response = array("success" => true,
"label" => $installLabel,
"text" => $installLabelText);
}
// Initializes the module by checking for/creating the required vpn_config directory in /root
private function initializeModule(){
$result = exec('cd /root && ls | grep vpn_config');
if($result == 'vpn_config'){
$result = "VPN Connect is ready!";
}else{
$this->execBackground('cd /root && mkdir vpn_config');
$result = exec('cd /root && ls | grep vpn_config');
if($result == 'vpn_config'){
$result = "VPN Connect is ready!";
}else{
$result = "VPN Connect setup failed :(";
}
}
//Get Available Certs
$certs = preg_grep('/^([^.])/', scandir("/root/vpn_config/"));
$cert_arr = array();
foreach ($certs as $cert){
array_push($cert_arr, (object)array('name' => $cert));
}
$this->response = array("success" => true,
"content" => $result,
"certs" => $cert_arr);
}
// Handles dependency installation and removal
private function handleDependencies(){
if($this->checkDependency('openvpn')){
exec('opkg remove openvpn-openssl');
$messsage = "Dependencies should now be removed! Note: the vpn_config directory is NOT removed in this process. Please wait for the page to refresh...";
}else{
$this->installDependency('openvpn-openssl');
$messsage = "Depedencies should now be installed! Please wait for the page to refresh...";
}
$this->response = array("success" => true,
"content" => $messsage);
}
// Builds the openvpn command string and calls it to star the VPN
private function startVPN(){
$inputData = $this->request->data;
$open_vpn_cmd = "openvpn --config ";
if($inputData[0] != ''){
$config_name = $inputData[0];
$open_vpn_cmd .= "/root/vpn_config/" . $config_name . " ";
}else{
$this->response = array("success" => false,
"content" => "Please specify a VPN config name..");
return;
}
if($inputData[1] != ''){
//Create password file for openvpn command to read in
$config_pass = $inputData[1];
$pass_file = fopen("/tmp/vpn_pass.txt", "w");
fwrite($pass_file, $config_pass);
fclose($pass_file);
$open_vpn_cmd .= "--auth-nocache --askpass /tmp/vpn_pass.txt ";
}
if($inputData[2] != ''){
$openvpn_flags = $inputData[2];
$open_vpn_cmd .= $openvpn_flags;
}
if($inputData[3] == true){
//Share VPN With Clients Connecting
$this->execBackground("iptables -t nat -A POSTROUTING -s 172.16.42.0/24 -o tun0 -j MASQUERADE");
$this->execBackground("iptables -A FORWARD -s 172.16.42.0/24 -o tun0 -j ACCEPT");
$this->execBackground("iptables -A FORWARD -d 172.16.42.0/24 -m state --state ESTABLISHED,RELATED -i tun0 -j ACCEPT");
}
$result = $this->execBackground($open_vpn_cmd);
$this->response = array("success" => true,
"content" => "VPN Running... ");
}
// Calls pkill to kill the OpenVPN process and stop the VPN
private function stopVPN(){
//Remove password file that could have been created, don't want any creds lying around ;)
unlink("/tmp/vpn_pass.txt");
exec("pkill openvpn");
$this->response = array("success" => true,
"content" => "VPN Stopped...");
}
// Uploads the .ovnp recieved from the service
private function uploadFile(){
$inputData = $this->request->file;
$fileName = $inputData[0];
$file = base64_decode($inputData[1]);
$response = [];
$name = $fileName;
$type = pathinfo($fileName, PATHINFO_EXTENSION);
// Do not accept any file other than .ovpn
if ($type != "ovpn") {
$this->response = array("success" => false);
return;
}
// Ensure the upload directory exists
if (!file_exists(__UPLOAD__)) {
if (!mkdir(__UPLOAD__, 0755, true)) {
$response[$name] = "Failed. Unable to upload because vpn_certs directory does not exist/could not be created!";
$this->response = array("success" => false);
return;
}
}
$uploadPath = __UPLOAD__ . $name;
$res = file_put_contents( $uploadPath, $file );
if ($res) {
$response[$name] = true;
} else {
$response[$name] = false;
}
$this->response = array("success" => $response[$name]);
}
}

222
OpenVPNConnect/js/module.js Normal file
View File

@ -0,0 +1,222 @@
/* Main AngularJS openVPNConnectController */
registerController('openVPNConnectController', ['$api', '$scope', '$timeout', '$window', '$http', function($api, $scope, $timeout, $window, $http) {
// Workspace Variables. Each value is populated by the form or displays to the form.
$scope.workspace = {config: "",
pass: "",
flags: "",
sharedconnection: false,
setupcontent: "",
outputcontent: "",
availablecerts: [],
uploadstatusLabel: "",
uploadstatus: ""};
/* Other variables used to display content or assist the dependency installation and file upload
functions
*/
$scope.content = "";
$scope.installLabel = "default";
$scope.installLabelText = "Checking..."
$scope.selectedFiles = [];
$scope.uploading = false;
// Call a function to install/uninstall dependencies for the module
$scope.handleDependencies = function(){
$scope.workspace.setupcontent = "Handling dependencies please wait...";
$api.request({
module: 'OpenVPNConnect',
action: 'handleDependencies',
}, function(response) {
if (response.success === true) {
$scope.workspace.setupcontent = response.content;
checkDependencies();
$timeout(function() {$window.location.reload();}, 5000);
}
//console.log(response) //Log the response to the console, this is useful for debugging.
});
}
/* Checks the current status of the dependencies for the module and displays
it via the dependency install/uninstall button to the user.
This is checked each time the app is loaded or when the user
installs/uninstalls dependencies manually
*/
var checkDependencies = function(){
$api.request({
module: 'OpenVPNConnect',
action: 'checkDependencies',
}, function(response) {
if (response.success === true) {
$scope.installLabel = response.label;
$scope.installLabelText = response.text;
}
//console.log(response) //Log the response to the console, this is useful for debugging.
});
}
// Call the checkDependencies function on page load
checkDependencies();
/* Initializes module by creating necessary folder structures and scanning for uploaded certs
this function is called each time the app is loaded to make sure the module is set up correctly
*/
var initializeModule = function(){
$api.request({
module: 'OpenVPNConnect',
action: 'initializeModule',
}, function(response) {
if (response.success === true) {
$scope.workspace.setupcontent = response.content;
for(var i = 0; i <= response.certs.length - 1; i++){
$scope.workspace.availablecerts.push(response.certs[i].name);
}
}
//console.log(response) //Log the response to the console, this is useful for debugging.
});
}
// Call the initializeModule function on page load
initializeModule();
/* Just calls the initializeModule function to refresh the cert list when the
user clicks the drop down menu button
*/
$scope.refreshCertList = function(){
$scope.workspace.availablecerts = [];
initializeModule();
}
// Sets the current config to use for the VPN connection
$scope.setConfig = function(cert){
$scope.workspace.config = cert;
}
/* Calls the startVPN function, passes all the form params to the API to run the OpenVPN command
Users can pass a config, an option password, and optional command line flags to run with the
openvpn command line utility. Also the shared connection open lets the user share the
VPN connection with its clients
*/
$scope.startVPN = function() {
$api.request({
module: 'OpenVPNConnect',
action: 'startVPN',
data: [$scope.workspace.config,
$scope.workspace.pass,
$scope.workspace.flags,
$scope.workspace.sharedconnection]
}, function(response) {
if (response.success === true) {
$scope.workspace.outputcontent = response.content;
}
//console.log(response) //Log the response to the console, this is useful for debugging.
});
}
// This function calls the API to kill the openvpn process and stop the VPN
$scope.stopVPN = function() {
$api.request({
module: 'OpenVPNConnect',
action: 'stopVPN'
}, function(response) {
if (response.success === true) {
$scope.workspace.outputcontent = response.content;
}
//console.log(response) //Log the response to the console, this is useful for debugging.
});
}
//File Upload Code, the first two functions prep the files for upload in the modal
$scope.setSelectedFiles = function(){
files = document.getElementById("selectedFiles").files;
for (var x = 0; x < files.length; x++) {
$scope.selectedFiles.push(files[x]);
}
};
$scope.removeSelectedFile = function(file){
var x = $scope.selectedFiles.length;
while (x--) {
if ($scope.selectedFiles[x] === file) {
$scope.selectedFiles.splice(x,1);
}
}
};
/* File upload function. Instaitates a FileReader object the makes a promise call to
doUpload once the async reader call is complete on each iteration
*/
$scope.uploadFile = function(){
$scope.uploading = true;
for (x = 0; x < $scope.selectedFiles.length; x++) {
var fileReader = new FileReader();
var fileName = $scope.selectedFiles[x].name;
var filesToUpload = $scope.selectedFiles.length;
readFile($scope.selectedFiles[x], fileName, filesToUpload);
}
};
// Read file function to handle a Promise for multiple file uploads using FilerReader
function readFile(file, file_name, files_to_upload){
return new Promise((resolve, reject) => {
var fr = new FileReader();
fr.onload = () => {
final_file = fr.result.split(',')[1]
resolve(doUpload(file_name, final_file, files_to_upload - 1));
};
fr.readAsDataURL(file);
});
}
/* Actually performs the upload request to the API. Passes the file name and a base64 encoded
file to be uploaded by the service
*/
var doUpload = function(file_name, file, files_to_upload){
$api.request({
module: 'OpenVPNConnect',
action: 'uploadFile',
file: [file_name,
file
]
}, function(response) {
if(response.success){
$scope.workspace.uploadstatusLabel = "Upload Success!";
$scope.workspace.uploadstatus = "success";
}else{
$scope.workspace.uploadstatusLabel = "One or more files failed to upload!";
$scope.workspace.uploadstatus = "danger";
}
if(files_to_upload === 0){
$scope.selectedFiles = [];
$scope.uploading = false;
}
//console.log(response) //Log the response to the console, this is useful for debugging.
});
};
}]);

151
OpenVPNConnect/module.html Normal file
View File

@ -0,0 +1,151 @@
<div class="col-md-12" ng-controller="openVPNConnectController">
<div class="panel-group">
<div class="col-md-12">
<!-- OpenVPN Connect Title Panel -->
<div class="panel panel-default">
<div class="panel-header text-muted text-center">
<h3>OpenVPN Connect
</h5>
</div>
<div id="main-panel" class="panel panel-default">
<div class="panel-body">
<!-- Dependency Installer UI -->
<div class="panel-body">
<div class="form-group">
<label class="control-label text-center">Dependencies:</label>
<button type="button" style="width: 90px;" class="btn btn-{{installLabel}} btn-xs" ng-click="handleDependencies()">{{installLabelText}}</button>
</div>
<div class="form-group">
<label class="control-label text-center">Setup Status</label>
<p>
<input class="form-control" ng-model="workspace.setupcontent" placeholder="" readonly></input>
</p>
</div>
</div>
<!-- OpenVPN Cert Selector -->
<div class="panel-body">
<form class="form-horizontal">
<div class="form-group">
<p>
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#ovpn_upload_view">Upload Cert</button>
</p>
<p>
<div>
<label class="control-label">
<a href="javascript:;" data-toggle="collapse" data-parent="#accordion" data-target="#collapseCertList">
<button type="button" class="btn btn-default" ng-click="refreshCertList()">
Select Cert +
</button>
</a>
</label>
<div id="collapseCertList" class="panel-collapse collapse">
<div class="panel panel-default">
<div>
<ul class="list-group text-center" ng-repeat="cert in workspace.availablecerts">
<li class="list-group-item">
<button type="button" class="btn btn-default" ng-click="setConfig(cert)">{{cert}}</button>
</ul>
</ul>
</div>
</div>
</div>
</div>
</p>
<!-- OpenVPN Connect Option Form -->
<label class="control-label text-center">VPN Config Name:</label>
<a href="javascript:;" ng-click="workspace.config = ''">Clear</a>
<p>
<input class="form-control" ng-model="workspace.config" placeholder="Enter input"></input>
</p>
<label class="control-label text-center">VPN Config Password (Optional):</label>
<a href="javascript:;" ng-click="workspace.pass = ''">Clear</a>
<p>
<input class="form-control" type="password" ng-model="workspace.pass" placeholder="Enter input"></input>
</p>
<label class="control-label text-center">OpenVPN Flags (Optional):</label>
<a href="javascript:;" ng-click="workspace.flags = ''">Clear</a>
<p>
<input class="form-control" ng-model="workspace.flags" placeholder="Enter input"></input>
</p>
<label class="control-label text-center">Share Connection With Clients:</label>
<input type="checkbox" ng-model="workspace.sharedconnection">
<div class="input-group">
<span class="input-group-btn text-center">
<button class="btn btn-default" type="button" ng-click="startVPN()">Start VPN</button>
<button class="btn btn-default" type="button" ng-click="stopVPN()">Stop VPN</button>
</span>
</div>
</div>
</form>
</div>
<!-- OpenVPN Status Section -->
<div class="panel-body">
<div class="form-group">
<label class="control-label text-center">OpenVPN Status</label>
<p>
<textarea class="form-control" rows="1" ng-model="workspace.outputcontent" placeholder="" readonly></textarea>
</p>
</div>
</div>
</div>
</div>
</div>
<!-- OpenVPN Cert Upload Modal -->
<div id="ovpn_upload_view" class="modal fade" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<div class="btn btn-primary">
<label for="selectedFiles" style="cursor: pointer">Add files</label>
</div>
</div>
<div class="modal-body">
<table class="table">
<thead>
<th>File</th>
<th>Actions</th>
</thead>
<tbody>
<tr ng-repeat="file in selectedFiles">
<td>{{ file.name }}</td>
<td>
<button class="btn btn-danger" ng-click="removeSelectedFile(file);">Remove
</td>
</tr>
</tbody>
</table>
<div style="text-align:center">
<input type="file" accept=".ovpn" id="selectedFiles" onchange="angular.element(this).scope().setSelectedFiles()" style="visibility: hidden;" multiple>
<img ng-show="uploading" ng-hide="!uploading">
<button class="btn" ng-show="!uploading" ng-hide="uploading" ng-disabled="selectedFiles.length == 0" ng-click="uploadFile();">Upload</button>
<div class="alert alert-{{workspace.uploadstatus}}">{{workspace.uploadstatusLabel}}</div>
</div>
</div>
</div>
</div>
</div>
<!-- Change Log Pannel -->
<div class="panel panel-default">
<div class="panel-header text-muted text-center">
<h5>
<a href="javascript:;" data-toggle="collapse" data-parent="#accordion" data-target="#collapseChangelog">Change Log</a>
</h5>
</div>
<div id="collapseChangelog" class="panel-collapse collapse">
<div class="panel-body">
<ul>
<li>
<b>1.0</b>
</li>
<ul>
<li class="text-muted">Initial Pineapple Nano Release</li>
</ul>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,10 @@
{
"author": "3ndG4me",
"description": "OpenVPN Connection Utility",
"devices": [
"nano",
"tetra"
],
"title": "OpenVPNConnect",
"version": "1.0"
}