start pool support, return NoAccess role, fix acl cache
authorDietmar Maurer <dietmar@proxmox.com>
Wed, 25 Jan 2012 12:54:36 +0000 (13:54 +0100)
committerDietmar Maurer <dietmar@proxmox.com>
Wed, 25 Jan 2012 13:32:12 +0000 (14:32 +0100)
PVE/API2/Group.pm
PVE/AccessControl.pm
PVE/RPCEnvironment.pm
test/Makefile
test/perm-test1.pl
test/perm-test4.pl
test/perm-test5.pl [new file with mode: 0755]
test/perm-test6.pl [new file with mode: 0755]
test/test5.cfg [new file with mode: 0644]
test/test6.cfg [new file with mode: 0644]

index 435e019..ddea3dc 100644 (file)
@@ -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}}) {
index 310a3f1..6afe65c 100644 (file)
@@ -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";
 
index 3df6035..15f7bad 100644 (file)
@@ -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}) || 
index a046dac..567a2e4 100644 (file)
@@ -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
 
index a80e648..fe654b8 100755 (executable)
@@ -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";
 
index 92ecfb2..c71322f 100755 (executable)
@@ -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 (executable)
index 0000000..697b959
--- /dev/null
@@ -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 (executable)
index 0000000..58ced5f
--- /dev/null
@@ -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 (file)
index 0000000..13948cf
--- /dev/null
@@ -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 (file)
index 0000000..7af1895
--- /dev/null
@@ -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: