Skip to content

Commit

Permalink
Feature network management (#1984)
Browse files Browse the repository at this point in the history
* refactor: turn network into routes and new virtual networks
* feat: network management
* feat: grant manage all networks
  • Loading branch information
frankiejol committed Jan 22, 2024
1 parent 369a0df commit 30a77a9
Show file tree
Hide file tree
Showing 43 changed files with 2,950 additions and 236 deletions.
141 changes: 140 additions & 1 deletion lib/Ravada.pm
Original file line number Diff line number Diff line change
Expand Up @@ -1641,6 +1641,12 @@ sub _add_indexes_generic($self) {
,"UNIQUE (name)"

]
,virtual_networks => [
"unique(id_vm,internal_id)"
,"unique(id_vm,name)"
,"index(date_changed)"
,"index(id_owner)"
]
);
my $if_not_exists = '';
$if_not_exists = ' IF NOT EXISTS ' if $CONNECTOR->dbh->{Driver}{Name} =~ /sqlite|mariadb/i;
Expand Down Expand Up @@ -1812,6 +1818,8 @@ sub _add_grants($self) {
$self->_add_grant('view_all',0,"The user can start and access the screen of any virtual machine");
$self->_add_grant('create_disk',0,'can create disk volumes');
$self->_add_grant('quota_disk',0,'disk space limit',1);
$self->_add_grant('create_networks',0,'can create virtual networks.');
$self->_add_grant('manage_all_networks',0,'can manage all the virtual networks.');
}

sub _add_grant($self, $grant, $allowed, $description, $is_int = 0, $default_admin=1) {
Expand Down Expand Up @@ -1889,6 +1897,7 @@ sub _enable_grants($self) {
,'start_limit', 'start_many'
,'view_all'
,'create_disk', 'quota_disk'
,'create_networks','manage_all_networks'
);

my $sth = $CONNECTOR->dbh->prepare("SELECT id,name FROM grant_types");
Expand Down Expand Up @@ -2362,6 +2371,24 @@ sub _sql_create_tables($self) {

}
]
,
[virtual_networks => {
id => 'integer PRIMARY KEY AUTO_INCREMENT',
,id_vm => 'integer NOT NULL references `vms` (`id`) ON DELETE CASCADE',
,name => 'varchar(200)'
,id_owner => 'integer NOT NULL references `users` (`id`) ON DELETE CASCADE',
,internal_id => 'char(80) not null'
,autostart => 'integer not null'
,bridge => 'char(80)'
,'ip_address' => 'char(20)'
,'ip_netmask' => 'char(20)'
,'dhcp_start' => 'char(15)'
,'dhcp_end' => 'char(15)'
,'is_active' => 'integer not null default 1'
,'is_public' => 'integer not null default 0'
,date_changed => 'timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'
}
]

);
for my $new_table (@tables ) {
Expand Down Expand Up @@ -5111,7 +5138,12 @@ sub _cmd_change_hardware {
my $user = Ravada::Auth::SQL->search_by_id($uid);

die "Error: User ".$user->name." not allowed\n"
if $hardware ne 'memory' && !$user->is_admin;
unless $user->is_admin
|| $hardware eq 'memory'
|| ($hardware eq 'network'
&& $user->can_change_hardware_network($domain, $data)
)
;

$domain->change_hardware(
$request->args('hardware')
Expand Down Expand Up @@ -5810,6 +5842,7 @@ sub _refresh_active_vms ($self) {
next;
}
$active_vm{$vm->id} = 1;
$vm->list_virtual_networks();
}
return \%active_vm;
}
Expand Down Expand Up @@ -6304,6 +6337,12 @@ sub _req_method {
,move_volume => \&_cmd_move_volume
,update_iso_urls => \&_cmd_update_iso_urls

,list_networks => \&_cmd_list_virtual_networks
,new_network => \&_cmd_new_network
,create_network => \&_cmd_create_network
,remove_network => \&_cmd_remove_network
,change_network => \&_cmd_change_network

);
return $methods{$cmd};
}
Expand Down Expand Up @@ -6723,6 +6762,106 @@ sub _cmd_remove_files($self, $request) {
$vm->remove_file(@file);
}

sub _cmd_list_virtual_networks($self, $request) {
my $user=Ravada::Auth::SQL->search_by_id($request->args('uid'));
die "Error: ".$user->name." not authorized\n"
unless $user->is_admin || $user->can_manage_all_networks || $user->can_create_networks;

my $id = $request->args('id_vm') or die "Error: missing id_vm";
my $vm = Ravada::VM->open($id);
my @list = $vm->list_virtual_networks();

$request->output(encode_json(\@list));
}


sub _cmd_new_network($self, $request) {
my $user=Ravada::Auth::SQL->search_by_id($request->args('uid'));
die "Error: ".$user->name." not authorized\n"
unless $user->can_create_networks;

$request->output(encode_json({}));

my $id = $request->args('id_vm') or die "Error: missing id_vm";
my $vm = Ravada::VM->open($id);
my $name = ($request->defined_arg('name') or 'net');

my $new = $vm->new_network($name);
$new = {} if !$new;

$request->output(encode_json( $new));
}

sub _cmd_create_network($self, $request) {
my $user=Ravada::Auth::SQL->search_by_id($request->args('uid'));
die "Error: ".$user->name." not authorized\n"
unless $user->can_create_networks || $user->can_manage_all_networks;

my $id = $request->args('id_vm') or die "Error: missing id_vm";
my $vm = Ravada::VM->open($id);
$request->output(encode_json({}));
my $id_net = $vm->create_network($request->args('data'),$request->args('uid')
, $request);
$request->output(encode_json({id_network => $id_net}));
}

sub _cmd_remove_network($self, $request) {

my $id = $request->args('id');
my $name = $request->defined_arg('name');
my $sth_net = $CONNECTOR->dbh->prepare(
"SELECT * FROM virtual_networks WHERE id=?"
);
$sth_net->execute($id);
my $network = $sth_net->fetchrow_hashref;
if ($network && $network->{id} ) {
_check_user_authorized_network($request, $id);
}
my $id_vm = ( $network->{id_vm} or $request->defined_arg('id_vm') );
die "Error: unknown id_vm ".Dumper([$network,$request]) if !$id_vm;

die "Error: unkonwn network , id=$id, name='".($name or '')."' "
.Dumper($network) if ! $network->{id} && !$name;

my $user=Ravada::Auth::SQL->search_by_id($request->args('uid'));

my $vm = Ravada::VM->open($id_vm);
$vm->remove_network($user, ($network->{id} or $name));
}

sub _check_user_authorized_network($request, $id_network) {

my $user=Ravada::Auth::SQL->search_by_id($request->args('uid'));

my $sth = $CONNECTOR->dbh->prepare(
"SELECT * FROM virtual_networks WHERE id=?"
);
$sth->execute($id_network);
my $network = $sth->fetchrow_hashref;

confess "Error: network $id_network not found" if !$network->{id};

die "Error: ".$user->name." not authorized\n"
unless $user->is_admin
|| $user->can_manage_all_networks
|| ( $user->can_create_networks && $network->{id_owner} == $user->id);

return $network;
}

sub _cmd_change_network($self, $request) {

my $data = $request->args('data');
die "Error: network.id required" if !exists $data->{id} || !$data->{id};

my $network = _check_user_authorized_network($request, $data->{id});

$data->{internal_id} = $network->{internal_id} if !$data->{internal_id};
my $vm = Ravada::VM->open($network->{id_vm});

$vm->change_network($data, $request->args('uid'));
}

sub _cmd_active_storage_pool($self, $request) {
my $user = Ravada::Auth::SQL->search_by_id($request->args('uid'));
die "Error: ".$user->name." not authorized to manage storage pools"
Expand Down
57 changes: 57 additions & 0 deletions lib/Ravada/Auth/SQL.pm
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ sub is_operator {
|| $self->can_view_groups()
|| $self->can_manage_groups()
|| $self->can_view_all()
|| $self->can_create_networks()
;
return 0;
}
Expand Down Expand Up @@ -635,9 +636,25 @@ sub remove($self) {
my $sth = $$CON->dbh->prepare("DELETE FROM grants_user where id_user=?");
$sth->execute($self->id);

$self->_remove_networks();

$sth = $$CON->dbh->prepare("DELETE FROM users where id=?");
$sth->execute($self->id);
$sth->finish;

}

sub _remove_networks($self) {
my $sth = $$CON->dbh->prepare("SELECT id,id_vm,name FROM virtual_networks WHERE id_owner=?");
$sth->execute($self->id);
while (my ($id, $id_vm, $name) = $sth->fetchrow) {
Ravada::Request->remove_network(
uid => Ravada::Utils::user_daemon->id
,id_vm => $id_vm
,id => $id
,name => $name
);
}
}

=head2 can_do
Expand Down Expand Up @@ -1235,6 +1252,46 @@ sub disk_used($self) {
return $used;
}

sub _load_network($network) {
confess "Error: undefined network"
if !defined $network;

my $sth = $$CON->dbh->prepare(
"SELECT * FROM virtual_networks where name=?"
);
$sth->execute($network);
my $row = $sth->fetchrow_hashref;

die "Error: network '$network' not found"
if !$row->{id};

lock_hash(%$row);
return $row;
}

=head2 can_change_hardware_network
Returns true if the user can change the network in a virtual machine,
false elsewhere
=cut

sub can_change_hardware_network($user, $domain, $data) {
return 1 if $user->is_admin;
return 1 if $user->can_manage_all_networks()
&& $domain->id_owner == $user->id;

confess "Error: undefined network ".Dumper($data)
if !exists $data->{network} || !defined $data->{network};

my $net = _load_network($data->{network});

return 1 if $user->id == $domain->id_owner
&& ( $net->{is_public} || $user->id == $net->{id_owner});
return 0;
}


sub AUTOLOAD($self, $domain=undef) {

my $name = $AUTOLOAD;
Expand Down
2 changes: 1 addition & 1 deletion lib/Ravada/Domain.pm
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ sub _around_start($orig, $self, @arg) {
if ( Ravada::setting(undef,"/backend/display_password") ) {
# We'll see if we set it from the network, defaults to 0 meanwhile
my $set_password = 0;
my $network = Ravada::Network->new(address => $remote_ip);
my $network = Ravada::Route->new(address => $remote_ip);
$set_password = 1 if $network->requires_password();
$arg{set_password} = $set_password;
}
Expand Down
22 changes: 20 additions & 2 deletions lib/Ravada/Domain/Void.pm
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,8 @@ sub _set_default_info($self, $listen_ip=undef) {
hwaddr => $info->{mac}
,address => $info->{ip}
,type => 'nat'
,driver => 'virtio'
,name => "net1"
};
$self->_store(hardware => $hardware );

Expand Down Expand Up @@ -921,6 +923,18 @@ sub _internal_autostart {
return $self->_value('autostart');
}

sub _new_network($self) {
my $hardware = $self->_value('hardware');
my $list = ( $hardware->{'network'} or [] );
my $data = {
hwaddr => _new_mac()
,address => ''
,type => 'nat'
,driver => 'virtio'
,name => "net".(scalar(@$list)+1)
};
}

sub set_controller($self, $name, $number=undef, $data=undef) {
my $hardware = $self->_value('hardware');

Expand All @@ -939,12 +953,16 @@ sub set_controller($self, $name, $number=undef, $data=undef) {
my @list2;
if (!defined $number) {
@list2 = @$list;
push @list2,($data or " $name z 1");
$data = $self->_new_network() if $name eq 'network' && !$data;
push @list2,($data or "$name z 1");
} else {
my $count = 0;
for my $item ( @$list ) {
$count++;
if ($number == $count) {
$data = $self->_new_network()
if $name eq 'network' && (!$data || ! keys %$data);

my $data2 = ( $data or " $name a ".($count+1));
$data2 = " $name b ".($count+1) if defined $data2 && ref($data2) && !keys %$data2;

Expand All @@ -953,7 +971,7 @@ sub set_controller($self, $name, $number=undef, $data=undef) {
}
$item = { driver => 'spice' , port => 'auto' , listen_ip => $self->_vm->listen_ip }
if $name eq 'display' && !defined $item;
push @list2,($item or " $name b ".($count+1));
push @list2,($item or " $name c ".($count+1));
}
}
$hardware->{$name} = \@list2;
Expand Down
Loading

0 comments on commit 30a77a9

Please sign in to comment.