From 4bc17477d841731a2ff88cf3d32ddb0fd0ac0899 Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Wed, 25 Jan 2012 13:54:36 +0100 Subject: [PATCH] start pool support, return NoAccess role, fix acl cache --- PVE/API2/Group.pm | 1 + PVE/AccessControl.pm | 65 ++++++++++++++++++++--- PVE/RPCEnvironment.pm | 116 ++++++++++++++++++++++++++++-------------- test/Makefile | 2 + test/perm-test1.pl | 2 +- test/perm-test4.pl | 2 +- test/perm-test5.pl | 42 +++++++++++++++ test/perm-test6.pl | 68 +++++++++++++++++++++++++ test/test5.cfg | 16 ++++++ test/test6.cfg | 21 ++++++++ 10 files changed, 288 insertions(+), 47 deletions(-) create mode 100755 test/perm-test5.pl create mode 100755 test/perm-test6.pl create mode 100644 test/test5.cfg create mode 100644 test/test6.cfg diff --git a/PVE/API2/Group.pm b/PVE/API2/Group.pm index 435e019..ddea3dc 100644 --- a/PVE/API2/Group.pm +++ b/PVE/API2/Group.pm @@ -62,6 +62,7 @@ __PACKAGE__->register_method ({ my $privs = [ 'User.Add', 'Sys.Audit' ]; my $allow = $rpcenv->check_any($authuser, "/access", $privs, 1); + syslog("info", "TEST $allow"); my $allowed_groups = $rpcenv->filter_groups($authuser, $privs, 1); foreach my $group (keys %{$usercfg->{groups}}) { diff --git a/PVE/AccessControl.pm b/PVE/AccessControl.pm index 310a3f1..6afe65c 100644 --- a/PVE/AccessControl.pm +++ b/PVE/AccessControl.pm @@ -663,12 +663,14 @@ sub add_role_privs { sub normalize_path { my $path = shift; - $path =~ s|/+|/|; + $path =~ s|/+|/|g; $path =~ s|/$||; $path = '/' if !$path; + $path = "/$path" if $path !~ m|^/|; + return undef if $path !~ m|^[[:alnum:]\-\_\/]+$|; return $path; @@ -916,6 +918,44 @@ sub parse_user_config { } else { warn "user config - ignore invalid path in acl '$pathtxt'\n"; } + } elsif ($et eq 'pool') { + my ($pathtxt, $comment, $vmlist, $storelist) = @data; + + if (my $path = normalize_path($pathtxt)) { + # make sure to add the pool (even if there are no members) + $cfg->{pools}->{$path} = { vms => {}, storage => {} } if !$cfg->{pools}->{$path}; + + $cfg->{pools}->{$path}->{comment} = PVE::Tools::decode_text($comment) if $comment; + + foreach my $vmid (split_list($vmlist)) { + if ($vmid !~ m/^\d+$/) { + warn "user config - ignore invalid vmid '$vmid' in pool '$path'\n"; + next; + } + $vmid = int($vmid); + + if ($cfg->{vms}->{$vmid}) { + warn "user config - ignore duplicate vmid '$vmid' in pool '$path'\n"; + next; + } + + $cfg->{pools}->{$path}->{vms}->{$vmid} = 1; + + # record vmid ==> pool relation + $cfg->{vms}->{$vmid} = $path; + } + + foreach my $storeid (split_list($storelist)) { + if ($storeid !~ m/^[a-z][a-z0-9\-\_\.]*[a-z0-9]$/i) { + warn "user config - ignore invalid storage '$storeid' in pool '$path'\n"; + next; + } + $cfg->{pools}->{$path}->{storage}->{$storeid} = 1; + } + + } else { + warn "user config - ignore invalid path in pool'$pathtxt'\n"; + } } else { warn "user config - ignore config line: $line\n"; } @@ -1138,6 +1178,16 @@ sub write_user_config { $data .= "\n"; + foreach my $path (keys %{$cfg->{pools}}) { + my $d = $cfg->{pools}->{$path}; + my $vmlist = join (',', keys %{$d->{vms}}); + my $storelist = join (',', keys %{$d->{storage}}); + my $comment = $d->{comment} ? PVE::Tools::encode_text($d->{comment}) : ''; + $data .= "pool:$path:$comment:$vmlist:$storelist:\n"; + } + + $data .= "\n"; + foreach my $role (keys %{$cfg->{roles}}) { next if $special_roles->{$role}; @@ -1206,6 +1256,9 @@ sub write_user_config { sub roles { my ($cfg, $user, $path) = @_; + # NOTE: we do not consider pools here. + # You need to use $rpcenv->roles() instead if you want that. + return 'Administrator' if $user eq 'root@pam'; # root can do anything my $perm = {}; @@ -1254,18 +1307,14 @@ sub roles { $perm = $new; # overwrite previous settings next; } - - #die "what herea?"; } - my $res = {}; - if (!defined ($perm->{NoAccess})) { - $res = $perm; - } + return ('NoAccess') if defined ($perm->{NoAccess}); + #return () if defined ($perm->{NoAccess}); #print "permission $user $path = " . Dumper ($perm); - my @ra = keys %$res; + my @ra = keys %$perm; #print "roles $user $path = " . join (',', @ra) . "\n"; diff --git a/PVE/RPCEnvironment.pm b/PVE/RPCEnvironment.pm index 3df6035..15f7bad 100644 --- a/PVE/RPCEnvironment.pm +++ b/PVE/RPCEnvironment.pm @@ -90,68 +90,109 @@ my $register_worker = sub { # ACL cache -my $compile_acl = sub { - my ($self, $user) = @_; +my $compile_acl_path = sub { + my ($self, $user, $path) = @_; - my $res = {}; my $cfg = $self->{user_cfg}; return undef if !$cfg->{roles}; - if ($user eq 'root@pam') { # root can do anything - return {'/' => $cfg->{roles}->{'Administrator'}}; - } + die "internal error" if $user eq 'root@pam'; - foreach my $path (sort keys %{$cfg->{acl}}) { - my @ra = PVE::AccessControl::roles($cfg, $user, $path); + my $cache = $self->{aclcache}; + $cache->{$user} = {} if !$cache->{$user}; + my $data = $cache->{$user}; - my $privs = {}; - foreach my $role (@ra) { - if (my $privset = $cfg->{roles}->{$role}) { - foreach my $p (keys %$privset) { - $privs->{$p} = 1; + if (!$data->{poolroles}) { + $data->{poolroles} = {}; + + foreach my $poolpath (keys %{$cfg->{pools}}) { + my $d = $cfg->{pools}->{$poolpath}; + my @ra = PVE::AccessControl::roles($cfg, $user, "/pool$poolpath"); # pool roles + next if !scalar(@ra); + foreach my $vmid (keys %{$d->{vms}}) { + for my $role (@ra) { + $data->{poolroles}->{"/vms/$vmid"}->{$role} = 1; + } + } + foreach my $storeid (keys %{$d->{storage}}) { + for my $role (@ra) { + $data->{poolroles}->{"/storage/$storeid"}->{$role} = 1; } } } + } + + my @ra = PVE::AccessControl::roles($cfg, $user, $path); - $res->{$path} = $privs; + # apply roles inherited from pools + # Note: assume we do not want to propagate those privs + if ($data->{poolroles}->{$path}) { + if (!($ra[0] && $ra[0] eq 'NoAccess')) { + foreach my $role (keys %{$data->{poolroles}->{$path}}) { + push @ra, $role; + } + } } - return $res; + $data->{roles}->{$path} = [ @ra ]; + + my $privs = {}; + foreach my $role (@ra) { + if (my $privset = $cfg->{roles}->{$role}) { + foreach my $p (keys %$privset) { + $privs->{$p} = 1; + } + } + } + $data->{privs}->{$path} = $privs; + + return $privs; }; +sub roles { + my ($self, $user, $path) = @_; + + if ($user eq 'root@pam') { # root can do anything + return ('Administrator'); + } + + $user = PVE::AccessControl::verify_username($user, 1); + return () if !$user; + + my $cache = $self->{aclcache}; + $cache->{$user} = {} if !$cache->{$user}; + + my $acl = $cache->{$user}; + + my $roles = $acl->{roles}->{$path}; + return @$roles if $roles; + + &$compile_acl_path($self, $user, $path); + $roles = $acl->{roles}->{$path} || []; + return @$roles; +} + sub permissions { my ($self, $user, $path) = @_; + if ($user eq 'root@pam') { # root can do anything + my $cfg = $self->{user_cfg}; + return $cfg->{roles}->{'Administrator'}; + } + $user = PVE::AccessControl::verify_username($user, 1); return {} if !$user; my $cache = $self->{aclcache}; + $cache->{$user} = {} if !$cache->{$user}; my $acl = $cache->{$user}; - if (!$acl) { - if (!($acl = &$compile_acl($self, $user))) { - return {}; - } - $cache->{$user} = $acl; - } - - my $perm; - - if (!($perm = $acl->{$path})) { - $perm = {}; - foreach my $p (sort keys %$acl) { - my $final = ($path eq $p); - - next if !(($p eq '/') || $final || ($path =~ m|^$p/|)); + my $perm = $acl->{privs}->{$path}; + return $perm if $perm; - $perm = $acl->{$p}; - } - $acl->{$path} = $perm; - } - - return $perm; + return &$compile_acl_path($self, $user, $path); } sub check { @@ -174,7 +215,7 @@ sub check_any { my ($self, $user, $path, $privs, $noerr) = @_; my $perm = $self->permissions($user, $path); - + syslog("info", "check_any $user $path " . join(" ", keys %$perm)); my $found = 0; foreach my $priv (@$privs) { PVE::AccessControl::verify_privname($priv); @@ -469,6 +510,7 @@ sub init_request { my $ucdata = PVE::Tools::file_get_contents($userconfig); my $cfg = PVE::AccessControl::parse_user_config($userconfig, $ucdata); $self->{user_cfg} = $cfg; + #print Dumper($cfg); } else { my $ucvers = PVE::Cluster::cfs_file_version('user.cfg'); if (!$self->{aclcache} || !defined($self->{aclversion}) || diff --git a/test/Makefile b/test/Makefile index a046dac..567a2e4 100644 --- a/test/Makefile +++ b/test/Makefile @@ -7,4 +7,6 @@ check: perl -I.. perm-test2.pl perl -I.. perm-test3.pl perl -I.. perm-test4.pl + perl -I.. perm-test5.pl + perl -I.. perm-test6.pl diff --git a/test/perm-test1.pl b/test/perm-test1.pl index a80e648..fe654b8 100755 --- a/test/perm-test1.pl +++ b/test/perm-test1.pl @@ -58,7 +58,7 @@ check_permission('alex@pve', '/vms/100', 'VM.Audit,VM.PowerMgmt'); check_roles('max@pve', '/vms/200', 'storage_manager'); check_roles('joe@pve', '/vms/200', 'vm_admin'); -check_roles('sue@pve', '/vms/200', ''); +check_roles('sue@pve', '/vms/200', 'NoAccess'); print "all tests passed\n"; diff --git a/test/perm-test4.pl b/test/perm-test4.pl index 92ecfb2..c71322f 100755 --- a/test/perm-test4.pl +++ b/test/perm-test4.pl @@ -25,7 +25,7 @@ sub check_roles { check_roles('User1@pve', '/vms/300', 'Role1'); -check_roles('User2@pve', '/vms/300', ''); +check_roles('User2@pve', '/vms/300', 'NoAccess'); print "all tests passed\n"; diff --git a/test/perm-test5.pl b/test/perm-test5.pl new file mode 100755 index 0000000..697b959 --- /dev/null +++ b/test/perm-test5.pl @@ -0,0 +1,42 @@ +#!/usr/bin/perl -w + +use strict; +use PVE::Tools; +use PVE::AccessControl; +use PVE::RPCEnvironment; +use Getopt::Long; + +my $rpcenv = PVE::RPCEnvironment->init('cli'); + +my $cfgfn = "test5.cfg"; +$rpcenv->init_request(userconfig => $cfgfn); + +sub check_roles { + my ($user, $path, $expected_result) = @_; + + my @ra = $rpcenv->roles($user, $path); + my $res = join(',', sort @ra); + + die "unexpected result\nneed '${expected_result}'\ngot '$res'\n" + if $res ne $expected_result; + + print "ROLES:$path:$user:$res\n"; +} + + +check_roles('User1@pve', '/vms', 'Role1'); +check_roles('User1@pve', '/vms/100', 'Role1'); +check_roles('User1@pve', '/vms/100/a', 'Role1'); +check_roles('User1@pve', '/vms/100/a/b', 'Role2'); +check_roles('User1@pve', '/vms/100/a/b/c', 'Role2'); +check_roles('User1@pve', '/vms/200', 'Role1'); + +check_roles('User2@pve', '/kvm', 'Role2'); +check_roles('User2@pve', '/kvm/vms', 'Role1'); +check_roles('User2@pve', '/kvm/vms/100', ''); +check_roles('User2@pve', '/kvm/vms/100/a', 'Role3'); +check_roles('User2@pve', '/kvm/vms/100/a/b', ''); + +print "all tests passed\n"; + +exit (0); diff --git a/test/perm-test6.pl b/test/perm-test6.pl new file mode 100755 index 0000000..58ced5f --- /dev/null +++ b/test/perm-test6.pl @@ -0,0 +1,68 @@ +#!/usr/bin/perl -w + +use strict; +use PVE::Tools; +use PVE::AccessControl; +use PVE::RPCEnvironment; +use Getopt::Long; + +my $rpcenv = PVE::RPCEnvironment->init('cli'); + +my $cfgfn = "test6.cfg"; +$rpcenv->init_request(userconfig => $cfgfn); + +sub check_roles { + my ($user, $path, $expected_result) = @_; + + my @ra = $rpcenv->roles($user, $path); + my $res = join(',', sort @ra); + + die "unexpected result\nneed '${expected_result}'\ngot '$res'\n" + if $res ne $expected_result; + + print "ROLES:$path:$user:$res\n"; +} + +check_roles('User1@pve', '', ''); +check_roles('User2@pve', '', ''); +check_roles('User3@pve', '', ''); +check_roles('User4@pve', '', ''); + +check_roles('User1@pve', '/vms', 'RoleTEST1'); +check_roles('User2@pve', '/vms', 'RoleTEST1'); +check_roles('User3@pve', '/vms', 'NoAccess'); +check_roles('User4@pve', '/vms', ''); + +check_roles('User1@pve', '/vms/100', 'RoleTEST1'); +check_roles('User2@pve', '/vms/100', 'RoleTEST1'); +check_roles('User3@pve', '/vms/100', 'NoAccess'); +check_roles('User4@pve', '/vms/100', ''); + +check_roles('User1@pve', '/vms/300', 'Role1'); +check_roles('User2@pve', '/vms/300', 'RoleTEST1'); +check_roles('User3@pve', '/vms/300', 'NoAccess'); +check_roles('User4@pve', '/vms/300', 'Role1'); + +check_roles('User1@pve', '/vms/500', 'RoleDEVEL,RoleTEST1'); +check_roles('User2@pve', '/vms/500', 'RoleDEVEL,RoleTEST1'); +check_roles('User3@pve', '/vms/500', 'NoAccess'); +check_roles('User4@pve', '/vms/500', ''); + +check_roles('User1@pve', '/vms/600', 'RoleMARKETING,RoleTEST1'); +check_roles('User2@pve', '/vms/600', 'RoleTEST1'); +check_roles('User3@pve', '/vms/600', 'NoAccess'); +check_roles('User4@pve', '/vms/600', 'RoleMARKETING'); + +check_roles('User1@pve', '/storage/store1', 'RoleDEVEL,RoleMARKETING'); +check_roles('User2@pve', '/storage/store1', 'RoleDEVEL'); +check_roles('User3@pve', '/storage/store1', 'RoleDEVEL'); +check_roles('User4@pve', '/storage/store1', 'RoleMARKETING'); + +check_roles('User1@pve', '/storage/store2', 'RoleDEVEL'); +check_roles('User2@pve', '/storage/store2', 'RoleDEVEL'); +check_roles('User3@pve', '/storage/store2', 'RoleDEVEL'); +check_roles('User4@pve', '/storage/store2', ''); + +print "all tests passed\n"; + +exit (0); diff --git a/test/test5.cfg b/test/test5.cfg new file mode 100644 index 0000000..13948cf --- /dev/null +++ b/test/test5.cfg @@ -0,0 +1,16 @@ +user:User1@pve:1: +user:User2@pve:1: + +group:GroupA:User1@pve,User2@pve: +group:GroupB:User1@pve,User2@pve: + +role:Role1:VM.PowerMgmt: +role:Role2:VM.Console: +role:Role3:VM.Console: + +acl:1:/vms:User1@pve:Role1: +acl:1:/vms/100/a/b:User1@pve:Role2: + +acl:0:/kvm:User2@pve:Role2: +acl:0:/kvm/vms:User2@pve:Role1: +acl:0:/kvm/vms/100/a:User2@pve:Role3: diff --git a/test/test6.cfg b/test/test6.cfg new file mode 100644 index 0000000..7af1895 --- /dev/null +++ b/test/test6.cfg @@ -0,0 +1,21 @@ +user:User1@pve:1: +user:User2@pve:1: +user:User3@pve:1: +user:User4@pve:1: + +group:DEVEL:User1@pve,User2@pve,User3@pve: +group:MARKETING:User1@pve,User4@pve: + +role:RoleDEVEL:VM.PowerMgmt: +role:RoleMARKETING:VM.Console: +role:RoleTEST1:VM.Console: + +acl:1:/pool/devel:@DEVEL:RoleDEVEL: +acl:1:/pool/marketing:@MARKETING:RoleMARKETING: + +acl:1:/vms:@DEVEL:RoleTEST1: +acl:1:/vms:User3@pve:NoAccess: +acl:1:/vms/300:@MARKETING:Role1: + +pool:devel:MITS development:500,501,502:store1 store2: +pool:marketing:MITS marketing:600:store1: -- 2.39.2