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}}) {
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;
} 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";
}
$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};
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 = {};
$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";
# 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 {
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);
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}) ||
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
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";
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";
--- /dev/null
+#!/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);
--- /dev/null
+#!/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);
--- /dev/null
+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:
--- /dev/null
+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: