Initial commit.
This commit is contained in:
commit
93b5bf300a
28
Build.PL
Executable file
28
Build.PL
Executable file
@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env perl
|
||||
use Module::Build;
|
||||
|
||||
my $home = $ENV{HOME};
|
||||
|
||||
my $build = Module::Build->new(
|
||||
module_name => 'VPNManager',
|
||||
license => 'AGPLv3',
|
||||
dist_author => 'Sergio Iglesias <contact@owlcode.tech>',
|
||||
dist_abstract => 'Manage Wireguard.',
|
||||
requires => {
|
||||
'DBI' => 0,
|
||||
'DBD::SQLite' => 0,
|
||||
'Path::Tiny' => 0,
|
||||
'DBIx::Class' => 0,
|
||||
'Mojolicious' => 0,
|
||||
'Moo' => 0,
|
||||
'Crypt::Bcrypt' => 0,
|
||||
'Crypt::URandom' => 0,
|
||||
'Capture::Tiny' => 0,
|
||||
},
|
||||
test_requires => {
|
||||
'Test::MockModule' => 0,
|
||||
'Test::Most' => 0,
|
||||
'Test::MockObject' => 0,
|
||||
}
|
||||
);
|
||||
$build->create_build_script;
|
70
lib/VPNManager.pm
Normal file
70
lib/VPNManager.pm
Normal file
@ -0,0 +1,70 @@
|
||||
package VPNManager;
|
||||
|
||||
use v5.38.2;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Mojo::Base 'Mojolicious', -signatures;
|
||||
|
||||
use Path::Tiny;
|
||||
|
||||
# This method will run once at server start
|
||||
sub startup ($self) {
|
||||
|
||||
# Load configuration from config file
|
||||
system 'chmod', '600', path(__FILE__)->parent->parent->child('v_p_n_manager.yml');
|
||||
my $config = $self->plugin('NotYAMLConfig');
|
||||
|
||||
# Configure the application
|
||||
$self->secrets( $config->{secrets} );
|
||||
|
||||
# Router
|
||||
my $r = $self->routes;
|
||||
|
||||
# Normal route to controller
|
||||
my $routes = $r->under(
|
||||
'/',
|
||||
sub {
|
||||
my $c = shift;
|
||||
my $redirect_login = sub {
|
||||
my $c = shift;
|
||||
my $url = Mojo::URL->new('/login');
|
||||
$url->query( redirect_to => $c->url_for );
|
||||
$c->redirect_to($url);
|
||||
return 0;
|
||||
};
|
||||
|
||||
if ( $c->url_for->path =~ /^\/login\/?$/ ) {
|
||||
return 1;
|
||||
}
|
||||
if ( !defined $c->session->{user} ) {
|
||||
return $redirect_login->($c);
|
||||
}
|
||||
require VPNManager::Schema;
|
||||
my $schema = VPNManager::Schema->Schema;
|
||||
my $resultset_admins = $schema->resultset('Admin');
|
||||
my ($user) = $resultset_admins->search(
|
||||
{
|
||||
username => $c->session->{user},
|
||||
}
|
||||
);
|
||||
if ( !defined $user ) {
|
||||
delete $c->session->{user};
|
||||
return $redirect_login->($c);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
);
|
||||
$routes->get('/')->to('Main#main');
|
||||
$routes->get('/login')->to('Main#login');
|
||||
$routes->post('/login')->to('Main#login_post');
|
||||
$routes->get('/vpn/create-user')->to('Main#create_vpn_user');
|
||||
$routes->post('/vpn/create-user')->to('Main#create_vpn_user_post');
|
||||
$routes->get('/vpn/user/:id/details')->to('Main#show_user_details');
|
||||
$routes->post('/vpn/user/:id/download')->to('Main#download_file');
|
||||
$routes->post('/vpn/user/:id/enable')->to('Main#enable_user');
|
||||
$routes->post('/vpn/user/:id/disable')->to('Main#disable_user');
|
||||
# $routes->post('/vpn/save')->to('Main#save_vpn_settings');
|
||||
}
|
||||
1;
|
11
lib/VPNManager/Controller/Example.pm
Normal file
11
lib/VPNManager/Controller/Example.pm
Normal file
@ -0,0 +1,11 @@
|
||||
package VPNManager::Controller::Example;
|
||||
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
||||
|
||||
# This action will render a template
|
||||
sub welcome ($self) {
|
||||
|
||||
# Render template "example/welcome.html.ep" with message
|
||||
$self->render(msg => 'Welcome to the Mojolicious real-time web framework!');
|
||||
}
|
||||
|
||||
1;
|
188
lib/VPNManager/Controller/Main.pm
Normal file
188
lib/VPNManager/Controller/Main.pm
Normal file
@ -0,0 +1,188 @@
|
||||
package VPNManager::Controller::Main;
|
||||
|
||||
use v5.38.2;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Crypt::Bcrypt qw/bcrypt_check/;
|
||||
use VPNManager::Schema;
|
||||
use Capture::Tiny qw/capture_stdout/;
|
||||
|
||||
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
||||
use Path::Tiny;
|
||||
|
||||
sub main($self) {
|
||||
my $resultset = VPNManager::Schema->Schema->resultset('VPNUser');
|
||||
my @users = $resultset->search( {} );
|
||||
$self->stash( users => \@users );
|
||||
$self->render( template => 'main/index' );
|
||||
}
|
||||
|
||||
sub login($self) {
|
||||
$self->render( template => 'main/login' );
|
||||
}
|
||||
|
||||
sub login_post($self) {
|
||||
my $password = $self->param('password');
|
||||
my $username = $self->param('username');
|
||||
my ($user) = VPNManager::Schema->Schema->resultset('Admin')->search(
|
||||
{
|
||||
username => $username
|
||||
}
|
||||
);
|
||||
if ( !defined $user ) {
|
||||
return $self->_invalid_login;
|
||||
}
|
||||
if ( !bcrypt_check $password, $user->password ) {
|
||||
return $self->_invalid_login;
|
||||
}
|
||||
$self->session->{user} = $username;
|
||||
return $self->redirect_to('/');
|
||||
}
|
||||
|
||||
sub _invalid_login($self) {
|
||||
$self->render( text => 'Invalid login', status => 401 );
|
||||
}
|
||||
|
||||
sub create_vpn_user($self) {
|
||||
$self->render( template => 'vpn/create-user' );
|
||||
}
|
||||
|
||||
sub create_vpn_user_post($self) {
|
||||
my $name = $self->param('name');
|
||||
my $config = $self->config;
|
||||
my $starting_ip = $config->{vpnclients}{starting_ip};
|
||||
my $resultset = VPNManager::Schema->Schema->resultset('VPNUser');
|
||||
my ($last_ip_user) = $resultset->search(
|
||||
{},
|
||||
{
|
||||
order_by => { -desc => 'ip' },
|
||||
rows => 1,
|
||||
}
|
||||
);
|
||||
my $ip = $starting_ip;
|
||||
my $there_is_previous_user = 0;
|
||||
if ( defined $last_ip_user ) {
|
||||
$ip = $last_ip_user->ip_to_text;
|
||||
$there_is_previous_user = 1;
|
||||
}
|
||||
my $new_user = $resultset->new(
|
||||
{ name => $name, publickey => '', ip => '', is_enabled => 0 } );
|
||||
$new_user->ip_from_text($ip);
|
||||
$new_user->ip( $new_user->ip + 1 ) if $there_is_previous_user;
|
||||
$ip = $new_user->ip_to_text;
|
||||
$new_user->insert;
|
||||
$new_user = $new_user->get_from_storage;
|
||||
my $id = $new_user->id;
|
||||
my $url = Mojo::URL->new("/vpn/user/$id/details");
|
||||
return $self->redirect_to($url);
|
||||
}
|
||||
|
||||
sub download_file($self) {
|
||||
my $id = $self->param('id');
|
||||
my $resultset = VPNManager::Schema->Schema->resultset('VPNUser');
|
||||
my ($user) = $resultset->search( { id => $id } );
|
||||
if ( !defined $user ) {
|
||||
return $self->render( text => 'No such user', status => 400 );
|
||||
}
|
||||
my $private_key = `wg genkey`;
|
||||
my $public_key = capture_stdout sub {
|
||||
open my $fh, '|-', 'wg', 'pubkey';
|
||||
print $fh $private_key;
|
||||
};
|
||||
chomp $private_key;
|
||||
$user->update( { publickey => $public_key } );
|
||||
my $config = $self->config;
|
||||
my $ip = $user->ip_to_text;
|
||||
my $vpn_file = <<"EOF";
|
||||
[Interface]
|
||||
PrivateKey = $private_key
|
||||
Address = $ip/32
|
||||
DNS = @{[$config->{vpn}{host}]}
|
||||
|
||||
[Peer]
|
||||
PublicKey = @{[$config->{vpn}{privkey}]}
|
||||
AllowedIPs = @{[$config->{vpnclients}{allowedips}]}
|
||||
Endpoint = @{[$config->{vpnclients}{endpoint}]}:@{[$config->{vpnclients}{server_port}]}
|
||||
EOF
|
||||
my $filename = $user->name . '-vpn.conf';
|
||||
$self->res->headers->add( 'Content-Type',
|
||||
'application/x-download;name=' . $filename );
|
||||
$self->res->headers->add( 'Content-Disposition',
|
||||
'attachment;filename=' . $filename );
|
||||
$self->render( data => $vpn_file );
|
||||
}
|
||||
|
||||
sub show_user_details($self) {
|
||||
my $id = $self->param('id');
|
||||
my $resultset = VPNManager::Schema->Schema->resultset('VPNUser');
|
||||
my ($user) = $resultset->search( { id => $id } );
|
||||
if ( !defined $user ) {
|
||||
return $self->render( text => 'No such user', status => 400 );
|
||||
}
|
||||
$self->stash( details_user => $user );
|
||||
return $self->render( template => 'vpn/user-details' );
|
||||
}
|
||||
|
||||
sub enable_user($self) {
|
||||
my $id = $self->param('id');
|
||||
my $resultset = VPNManager::Schema->Schema->resultset('VPNUser');
|
||||
my ($user) = $resultset->search( { id => $id } );
|
||||
if ( !defined $user ) {
|
||||
return $self->render( text => 'No such user', status => 400 );
|
||||
}
|
||||
if ( $user->publickey eq '' ) {
|
||||
return $self->render(
|
||||
text => 'You must first download the vpn file',
|
||||
status => 400
|
||||
);
|
||||
}
|
||||
$user->update( { is_enabled => 1 } );
|
||||
return $self->redirect_to('/');
|
||||
}
|
||||
|
||||
sub disable_user($self) {
|
||||
my $id = $self->param('id');
|
||||
my $resultset = VPNManager::Schema->Schema->resultset('VPNUser');
|
||||
my ($user) = $resultset->search( { id => $id } );
|
||||
if ( !defined $user ) {
|
||||
return $self->render( text => 'No such user', status => 400 );
|
||||
}
|
||||
return $self->render( text => 'This user is protected', status => 400 )
|
||||
if $user->is_protected;
|
||||
$user->update( { is_enabled => 0 } );
|
||||
return $self->redirect_to('/');
|
||||
}
|
||||
|
||||
#sub save_vpn_settings($self) {
|
||||
# my $out_dir = path(__FILE__)->parent->parent->parent->parent->child('out');
|
||||
# $out_dir->mkpath;
|
||||
# system 'chmod', '700', $out_dir;
|
||||
# my $config = $self->config;
|
||||
# my $vpn_config = <<"EOF";
|
||||
#[Interface]
|
||||
#Address = @{[$config->{vpn}{host}]}/@{[$config->{vpn}{submask}]}
|
||||
#MTU = @{[$config->{vpn}{mtu}]}
|
||||
#SaveConfig = false
|
||||
#ListenPort = @{[$config->{vpnclients}{server_port}]}
|
||||
#PrivateKey = @{[$config->{vpn}{privkey}]}
|
||||
#EOF
|
||||
# my $resultset = VPNManager::Schema->Schema->resultset('VPNUser');
|
||||
# my @users = $resultset->search( {} );
|
||||
#
|
||||
# for my $user (@users) {
|
||||
# next if !$user->is_enabled;
|
||||
#
|
||||
# $vpn_config .= <<"EOF";
|
||||
#
|
||||
#[Peer]
|
||||
#PublicKey = @{[$user->publickey]}
|
||||
#AllowedIPs = @{[$user->ip_to_text]}/32
|
||||
#Endpoint = @{[$config->{vpn}{endpoint}]}:@{[$config->{vpnclients}{server_port}]}
|
||||
#EOF
|
||||
# }
|
||||
# $out_dir->child('wg0.conf')->spew_raw($vpn_config);
|
||||
# return $self->redirect_to('/');
|
||||
#}
|
||||
1;
|
116
lib/VPNManager/DB.pm
Normal file
116
lib/VPNManager/DB.pm
Normal file
@ -0,0 +1,116 @@
|
||||
package VPNManager::DB;
|
||||
|
||||
use v5.38.2;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use feature 'signatures';
|
||||
|
||||
use DBI;
|
||||
use DBD::SQLite;
|
||||
|
||||
use VPNManager::DB::Migrations;
|
||||
use Path::Tiny;
|
||||
use Data::Dumper;
|
||||
|
||||
my $dbh;
|
||||
|
||||
sub reset_dbh {
|
||||
undef $dbh;
|
||||
}
|
||||
|
||||
sub connect {
|
||||
if ( defined $dbh ) {
|
||||
return $dbh;
|
||||
}
|
||||
my $class = shift;
|
||||
require VPNManager;
|
||||
my $app = VPNManager->new;
|
||||
my $config = $app->config;
|
||||
my $db_path = $class->_db_path;
|
||||
$dbh = DBI->connect(
|
||||
'dbi:SQLite:dbname='.$db_path,
|
||||
undef, undef,
|
||||
{
|
||||
RaiseError => 1,
|
||||
},
|
||||
);
|
||||
$class->_migrate($dbh);
|
||||
return $dbh;
|
||||
}
|
||||
|
||||
sub _db_path($class) {
|
||||
my $home = $ENV{HOME};
|
||||
my $db_path = '';
|
||||
{
|
||||
$db_path = $home . '/' if $home;
|
||||
}
|
||||
$db_path .= '.vpnmanager/db.sqlite';
|
||||
path($db_path)->parent->mkpath;
|
||||
system 'chmod', '-v', '700', path($db_path)->parent;
|
||||
return $db_path;
|
||||
}
|
||||
|
||||
sub _migrate {
|
||||
my $class = shift;
|
||||
my $dbh = shift;
|
||||
local $dbh->{RaiseError} = 0;
|
||||
local $dbh->{PrintError} = 0;
|
||||
my @migrations = VPNManager::DB::Migrations::MIGRATIONS();
|
||||
if ( $class->get_current_migration($dbh) > @migrations ) {
|
||||
warn "Something happened there, wrong migration number.";
|
||||
}
|
||||
if ( $class->get_current_migration($dbh) >= @migrations ) {
|
||||
say STDERR "Migrations already applied.";
|
||||
return;
|
||||
}
|
||||
$class->_apply_migrations( $dbh, \@migrations );
|
||||
}
|
||||
|
||||
sub _apply_migrations {
|
||||
my $class = shift;
|
||||
my $dbh = shift;
|
||||
my $migrations = shift;
|
||||
for (
|
||||
my $i = $class->get_current_migration($dbh) ;
|
||||
$i < @$migrations ;
|
||||
$i++
|
||||
)
|
||||
{
|
||||
local $dbh->{RaiseError} = 1;
|
||||
my $current_migration = $migrations->[$i];
|
||||
my $migration_number = $i + 1;
|
||||
$class->_apply_migration( $dbh, $current_migration, $migration_number );
|
||||
}
|
||||
}
|
||||
|
||||
sub _apply_migration {
|
||||
my $class = shift;
|
||||
my $dbh = shift;
|
||||
my $current_migration = shift;
|
||||
my $migration_number = shift;
|
||||
{
|
||||
if (ref $current_migration eq 'CODE') {
|
||||
$current_migration->($dbh);
|
||||
next;
|
||||
}
|
||||
$dbh->do($current_migration);
|
||||
}
|
||||
$dbh->do( <<'EOF', undef, 'current_migration', $migration_number );
|
||||
INSERT INTO options
|
||||
VALUES ($1, $2)
|
||||
ON CONFLICT (name) DO
|
||||
UPDATE SET value = $2;
|
||||
EOF
|
||||
}
|
||||
|
||||
sub get_current_migration {
|
||||
my $class = shift;
|
||||
my $dbh = shift;
|
||||
my $result = $dbh->selectrow_hashref( <<'EOF', undef, 'current_migration' );
|
||||
select value from options where name = ?;
|
||||
EOF
|
||||
return int( $result->{value} // 0 );
|
||||
}
|
||||
1;
|
33
lib/VPNManager/DB/Migrations.pm
Normal file
33
lib/VPNManager/DB/Migrations.pm
Normal file
@ -0,0 +1,33 @@
|
||||
package VPNManager::DB::Migrations;
|
||||
|
||||
use v5.34.1;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use feature 'signatures';
|
||||
|
||||
sub MIGRATIONS {
|
||||
return (
|
||||
'CREATE TABLE options (
|
||||
name TEXT PRIMARY KEY,
|
||||
value TEXT
|
||||
);',
|
||||
'CREATE TABLE vpn_users (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
publickey TEXT NOT NULL,
|
||||
is_enabled INTEGER NOT NULL DEFAULT true,
|
||||
is_protected INTEGER NOT NULL DEFAULT true,
|
||||
is_deleted INTEGER NOT NULL DEFAULT false,
|
||||
ip INTEGER NOT NULL
|
||||
);',
|
||||
'CREATE TABLE admins (
|
||||
id INTEGER PRIMARY KEY,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password TEXT NOT NULL
|
||||
);',
|
||||
);
|
||||
}
|
||||
1;
|
36
lib/VPNManager/Schema.pm
Normal file
36
lib/VPNManager/Schema.pm
Normal file
@ -0,0 +1,36 @@
|
||||
package VPNManager::Schema;
|
||||
|
||||
use v5.36.0;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use feature 'signatures';
|
||||
|
||||
use parent 'DBIx::Class::Schema';
|
||||
|
||||
__PACKAGE__->load_namespaces();
|
||||
|
||||
my $schema;
|
||||
|
||||
sub Schema ($class) {
|
||||
if ( !defined $schema ) {
|
||||
require VPNManager::DB;
|
||||
VPNManager::DB->connect;
|
||||
my $db_path = VPNManager::DB->_db_path;
|
||||
my $user = undef;
|
||||
my $password = undef;
|
||||
# Undef is perfectly fine for username and password.
|
||||
$schema = $class->connect(
|
||||
'dbi:SQLite:dbname='.$db_path, $user, $password,
|
||||
{
|
||||
}
|
||||
);
|
||||
}
|
||||
return $schema;
|
||||
}
|
||||
|
||||
sub reset_schema {
|
||||
undef $schema;
|
||||
}
|
||||
1;
|
29
lib/VPNManager/Schema/Result/Admin.pm
Normal file
29
lib/VPNManager/Schema/Result/Admin.pm
Normal file
@ -0,0 +1,29 @@
|
||||
package VPNManager::Schema::Result::Admin;
|
||||
|
||||
use v5.38.2;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use feature 'signatures';
|
||||
|
||||
use parent 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table('admins');
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
id => {
|
||||
data_type => 'INTEGER',
|
||||
is_auto_increment => 1,
|
||||
},
|
||||
username => {
|
||||
data_type => 'TEXT',
|
||||
is_nullable => 0,
|
||||
},
|
||||
password => {
|
||||
data_type => 'TEXT',
|
||||
is_nullable => 0,
|
||||
},
|
||||
);
|
||||
__PACKAGE__->set_primary_key('id');
|
||||
1;
|
62
lib/VPNManager/Schema/Result/VPNUser.pm
Normal file
62
lib/VPNManager/Schema/Result/VPNUser.pm
Normal file
@ -0,0 +1,62 @@
|
||||
package VPNManager::Schema::Result::VPNUser;
|
||||
|
||||
use v5.38.2;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use feature 'signatures';
|
||||
|
||||
use parent 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table('vpn_users');
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
id => {
|
||||
data_type => 'INTEGER',
|
||||
is_auto_increment => 1,
|
||||
},
|
||||
name => {
|
||||
data_type => 'TEXT',
|
||||
is_nullable => 0,
|
||||
},
|
||||
publickey => {
|
||||
data_type => 'TEXT',
|
||||
is_nullable => 0,
|
||||
},
|
||||
is_enabled => {
|
||||
data_type => 'INTEGER',
|
||||
is_nullable => 1,
|
||||
},
|
||||
is_protected => {
|
||||
data_type => 'INTEGER',
|
||||
is_nullable => 1,
|
||||
},
|
||||
is_deleted => {
|
||||
data_type => 'INTEGER',
|
||||
is_nullable => 1,
|
||||
},
|
||||
ip => {
|
||||
data_type => 'INTEGER',
|
||||
is_nullable => 0,
|
||||
},
|
||||
);
|
||||
|
||||
sub ip_to_text($self) {
|
||||
my @octets;
|
||||
for my $i (0..3) {
|
||||
push @octets, ($self->ip >> (abs(3-$i) * 8)) & 0xff;
|
||||
}
|
||||
return join '.', @octets;
|
||||
}
|
||||
|
||||
sub ip_from_text($self, $ip) {
|
||||
my @octets = split /\./, $ip;
|
||||
my $raw_ip = 0;
|
||||
for my $i (0..3) {
|
||||
$raw_ip |= (($octets[$i] & 0xff) << (abs(3-$i) * 8));
|
||||
}
|
||||
$self->ip($raw_ip);
|
||||
}
|
||||
__PACKAGE__->set_primary_key('id');
|
||||
1;
|
31
public/style.css
Normal file
31
public/style.css
Normal file
@ -0,0 +1,31 @@
|
||||
body.login-body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
body.create-user {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
body.main {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
form.inline {
|
||||
display: inline;
|
||||
}
|
29
script/create_user.pl
Normal file
29
script/create_user.pl
Normal file
@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use v5.38.2;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Basename qw/dirname/;
|
||||
|
||||
use lib dirname(dirname(__FILE__)).'/lib';
|
||||
|
||||
use Crypt::Bcrypt qw/bcrypt/;
|
||||
use Crypt::URandom qw/urandom/;
|
||||
use VPNManager::Schema;
|
||||
|
||||
my $username = $ARGV[0] or die 'No username passed';
|
||||
my $password = $ARGV[1] or die 'No password passed';
|
||||
|
||||
my $new_salt = urandom(16);
|
||||
my $encrypted_password = bcrypt $password, '2b', 12, $new_salt;
|
||||
|
||||
VPNManager::Schema->Schema->resultset('Admin')->populate(
|
||||
[
|
||||
{
|
||||
username => $username,
|
||||
password => $encrypted_password,
|
||||
}
|
||||
]
|
||||
);
|
39
script/get_wg_config.pl
Normal file
39
script/get_wg_config.pl
Normal file
@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env perl
|
||||
use v5.38.2;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Moo;
|
||||
use File::Basename qw/dirname/;
|
||||
use lib dirname(dirname(__FILE__)).'/lib';
|
||||
use VPNManager::Schema;
|
||||
|
||||
sub get_vpn_settings($self) {
|
||||
require VPNManager;
|
||||
my $config = VPNManager->new->config;
|
||||
my $vpn_config = <<"EOF";
|
||||
[Interface]
|
||||
Address = @{[$config->{vpn}{host}]}/@{[$config->{vpn}{submask}]}
|
||||
MTU = @{[$config->{vpn}{mtu}]}
|
||||
SaveConfig = false
|
||||
ListenPort = @{[$config->{vpnclients}{server_port}]}
|
||||
PrivateKey = @{[$config->{vpn}{privkey}]}
|
||||
EOF
|
||||
my $resultset = VPNManager::Schema->Schema->resultset('VPNUser');
|
||||
my @users = $resultset->search( {} );
|
||||
|
||||
for my $user (@users) {
|
||||
next if !$user->is_enabled;
|
||||
|
||||
$vpn_config .= <<"EOF";
|
||||
|
||||
[Peer]
|
||||
PublicKey = @{[$user->publickey]}
|
||||
AllowedIPs = @{[$user->ip_to_text]}/32
|
||||
Endpoint = @{[$config->{vpn}{endpoint}]}:@{[$config->{vpnclients}{server_port}]}
|
||||
EOF
|
||||
}
|
||||
say $vpn_config;
|
||||
}
|
||||
__PACKAGE__->new->get_vpn_settings;
|
42
script/installer.pl
Normal file
42
script/installer.pl
Normal file
@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use v5.38.2;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use File::Basename qw/dirname/;
|
||||
use Cwd 'abs_path';
|
||||
|
||||
if ($> != 0) {
|
||||
die 'You must be root.';
|
||||
}
|
||||
|
||||
while (1) {
|
||||
eval {
|
||||
install_if_new();
|
||||
sleep 15;
|
||||
};
|
||||
if ($@) {
|
||||
warn $@;
|
||||
}
|
||||
}
|
||||
|
||||
sub install_if_new {
|
||||
my $script_get_wg_config = abs_path(dirname(__FILE__).'/get_wg_config.pl');
|
||||
my $user = 'vpnmanager';
|
||||
open my $fh, '-|', 'sudo', '-i', '-u', $user, 'perl', $script_get_wg_config;
|
||||
my $contents = join '', <$fh>;
|
||||
my $output_file = '/etc/wireguard/wg0.conf';
|
||||
my $output_exists;
|
||||
open $fh, '<', $output_file and ($output_exists = 1);
|
||||
my $contents_output_file = '';
|
||||
$contents_output_file = join '', <$fh> if $output_exists;
|
||||
if ($contents ne $contents_output_file) {
|
||||
say 'Writting new file';
|
||||
open $fh, '>', $output_file;
|
||||
print $fh $contents;
|
||||
system 'systemctl', 'restart', 'vpnmanager';
|
||||
return;
|
||||
}
|
||||
say 'Files equal';
|
||||
}
|
11
script/vpnmanager
Executable file
11
script/vpnmanager
Executable file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Mojo::File qw(curfile);
|
||||
use lib curfile->dirname->sibling('lib')->to_string;
|
||||
use Mojolicious::Commands;
|
||||
|
||||
# Start command line interface for application
|
||||
Mojolicious::Commands->start_app('VPNManager');
|
5
templates/layouts/default.html.ep
Normal file
5
templates/layouts/default.html.ep
Normal file
@ -0,0 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><title><%= title %></title></head>
|
||||
<body><%= content %></body>
|
||||
</html>
|
22
templates/main/index.html.ep
Normal file
22
templates/main/index.html.ep
Normal file
@ -0,0 +1,22 @@
|
||||
% my $users = stash 'users';
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="/style.css"/>
|
||||
</head>
|
||||
<body class="main">
|
||||
<p><a href="/vpn/create-user">Create new vpn user</a>.</p>
|
||||
% if (defined $users && @$users) {
|
||||
<ul>
|
||||
% for my $user (@$users) {
|
||||
<li>Id: <b><%=$user->id%></b> Name: <b><%=$user->name%></b> Internal IP Address: <b><%=$user->ip_to_text%></b> <a href="/vpn/user/<%=$user->id%>/details">View details</a>
|
||||
<form class="inline" method="post" action="/vpn/user/<%=$user->id%>/<%=$user->is_enabled ? 'disable' : 'enable'%>">
|
||||
<input type="submit" value="<%=$user->is_enabled? 'Disable' : 'Enable'%>"/>
|
||||
</form></li>
|
||||
% }
|
||||
% }
|
||||
</ul>
|
||||
<form action="/vpn/save" method="post">
|
||||
<input type="submit" value="Save VPN Settings"/>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
20
templates/main/login.html.ep
Normal file
20
templates/main/login.html.ep
Normal file
@ -0,0 +1,20 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="/style.css"/>
|
||||
</head>
|
||||
<body class="login-body">
|
||||
<form class="login-form" action="/login" method="post">
|
||||
<div class="login-field">
|
||||
<label for="username">Username</label>
|
||||
<input name="username" type="text"/>
|
||||
</div>
|
||||
<div class="login-field">
|
||||
<label for="password">Password</label>
|
||||
<input name="password" type="password"/>
|
||||
</div>
|
||||
<div class="login-field">
|
||||
<input type="submit" value="Submit"/>
|
||||
</div>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
14
templates/vpn/create-user.html.ep
Normal file
14
templates/vpn/create-user.html.ep
Normal file
@ -0,0 +1,14 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="/style.css"/>
|
||||
</head>
|
||||
<body class="create-user">
|
||||
<form action="/vpn/create-user" method="post">
|
||||
<div>
|
||||
<label for="name">Name</label>
|
||||
<input name="name"/>
|
||||
</div>
|
||||
<input type="submit" value="Submit"/>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
10
templates/vpn/download-file.html.ep
Normal file
10
templates/vpn/download-file.html.ep
Normal file
@ -0,0 +1,10 @@
|
||||
% use MIME::Base64 qw/encode_base64/;
|
||||
% my $file = stash 'vpn_file';
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="/style.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<a href="/vpn/user/<%=$user->id%>/download" download="wg0.conf">Dowload vpn file</a>
|
||||
</body>
|
||||
</html>
|
16
templates/vpn/user-details.html.ep
Normal file
16
templates/vpn/user-details.html.ep
Normal file
@ -0,0 +1,16 @@
|
||||
% use MIME::Base64 qw/encode_base64/;
|
||||
% my $user = stash 'details_user';
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="/style.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<h1>VPN Account</h1>
|
||||
<h2>Name: <%=$user->name%></h2>
|
||||
<h2>Internal IP Address: <%=$user->ip_to_text%></h2>
|
||||
<form action="/vpn/user/<%=$user->id%>/download" target="_blank" method="post">
|
||||
<input type="submit" value="Download new VPN file"/>
|
||||
</form>
|
||||
<p><a href="/">Volver al menu principal</a></p>
|
||||
</body>
|
||||
</html>
|
16
v_p_n_manager.example.yml
Normal file
16
v_p_n_manager.example.yml
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
secrets:
|
||||
- generated_secret
|
||||
vpn:
|
||||
host: '192.168.2.1'
|
||||
submask: 24
|
||||
privkey: 'your_private_key'
|
||||
listenport: 51820
|
||||
mtu: 1420
|
||||
endpoint: 'your_endpoint'
|
||||
vpnclients:
|
||||
submask: 32
|
||||
allowedips: '192.168.2.0/24'
|
||||
endpoint: 'your_endpoint'
|
||||
server_port: 51820
|
||||
starting_ip: '192.168.2.2'
|
Loading…
Reference in New Issue
Block a user