]> git.proxmox.com Git - pve-access-control.git/blobdiff - src/PVE/AccessControl.pm
bump version to 8.1.4
[pve-access-control.git] / src / PVE / AccessControl.pm
index 24c6618bdb470924b0a3b065a471982590ea2980..47f2d38b09c7f267e74978de506cb47cbdf8ae41 100644 (file)
@@ -16,6 +16,7 @@ use JSON;
 use Scalar::Util 'weaken';
 use URI::Escape;
 
+use PVE::Exception qw(raise_perm_exc raise_param_exc);
 use PVE::OTP;
 use PVE::Ticket;
 use PVE::Tools qw(run_command lock_file file_get_contents split_list safe_print);
@@ -753,6 +754,9 @@ sub authenticate_2nd_new_do : prototype($$$$) {
     my ($username, $realm, $tfa_response, $tfa_challenge) = @_;
     my ($tfa_cfg, $realm_tfa) = user_get_tfa($username, $realm);
 
+    # FIXME: `$tfa_cfg` is now usually never undef - use cheap check for
+    # whether the user has *any* entries here instead whe it is available in
+    # pve-rs
     if (!defined($tfa_cfg)) {
        return undef;
     }
@@ -805,6 +809,10 @@ sub authenticate_2nd_new_do : prototype($$$$) {
        $tfa_challenge = undef;
     } else {
        $tfa_challenge = $tfa_cfg->authentication_challenge($username);
+
+       die "missing required 2nd keys\n"
+           if $realm_tfa && !defined($tfa_challenge);
+
        if (defined($tfa_response)) {
            if (defined($tfa_challenge)) {
                $tfa_done = 1;
@@ -943,7 +951,7 @@ sub iterate_acl_tree {
 
     my $children = $node->{children};
 
-    foreach my $child (keys %$children) {
+    foreach my $child (sort keys %$children) {
        iterate_acl_tree("$path/$child", $children->{$child}, $code);
     }
 }
@@ -1058,6 +1066,7 @@ my $privgroups = {
            'Sys.PowerMgmt',
            'Sys.Modify', # edit/change node settings
            'Sys.Incoming', # incoming storage/guest migrations
+           'Sys.AccessNetwork', # for, e.g., downloading ISOs from any URL
        ],
        admin => [
            'Sys.Console',
@@ -1143,24 +1152,25 @@ my $special_roles = {
 
 sub create_roles {
 
-    foreach my $cat (keys %$privgroups) {
+    for my $cat (keys %$privgroups) {
        my $cd = $privgroups->{$cat};
-       foreach my $p (@{$cd->{root}}, @{$cd->{admin}},
-                      @{$cd->{user}}, @{$cd->{audit}}) {
-           $valid_privs->{$p} = 1;
+       # create map to easily check if a privilege is valid
+       for my $priv (@{$cd->{root}}, @{$cd->{admin}}, @{$cd->{user}}, @{$cd->{audit}}) {
+           $valid_privs->{$priv} = 1;
        }
-       foreach my $p (@{$cd->{admin}}, @{$cd->{user}}, @{$cd->{audit}}) {
-
-           $special_roles->{"PVE${cat}Admin"}->{$p} = 1;
-           $special_roles->{"PVEAdmin"}->{$p} = 1;
+       # create grouped admin roles and PVEAdmin
+       for my $priv (@{$cd->{admin}}, @{$cd->{user}}, @{$cd->{audit}}) {
+           $special_roles->{"PVE${cat}Admin"}->{$priv} = 1;
+           $special_roles->{"PVEAdmin"}->{$priv} = 1;
        }
+       # create grouped user and audit roles
        if (scalar(@{$cd->{user}})) {
-           foreach my $p (@{$cd->{user}}, @{$cd->{audit}}) {
-               $special_roles->{"PVE${cat}User"}->{$p} = 1;
+           for my $priv (@{$cd->{user}}, @{$cd->{audit}}) {
+               $special_roles->{"PVE${cat}User"}->{$priv} = 1;
            }
        }
-       foreach my $p (@{$cd->{audit}}) {
-           $special_roles->{"PVEAuditor"}->{$p} = 1;
+       for my $priv (@{$cd->{audit}}) {
+           $special_roles->{"PVEAuditor"}->{$priv} = 1;
        }
     }
 
@@ -1257,8 +1267,14 @@ sub check_path {
        |/nodes
        |/nodes/[[:alnum:]\.\-\_]+
        |/pool
-       |/pool/[[:alnum:]\.\-\_]+
+       |/pool/[A-Za-z0-9\.\-_]+(?:/[A-Za-z0-9\.\-_]+){0,2}
        |/sdn
+       |/sdn/controllers
+       |/sdn/controllers/[[:alnum:]\_\-]+
+       |/sdn/dns
+       |/sdn/dns/[[:alnum:]]+
+       |/sdn/ipams
+       |/sdn/ipams/[[:alnum:]]+
        |/sdn/zones
        |/sdn/zones/[[:alnum:]\.\-\_]+
        |/sdn/zones/[[:alnum:]\.\-\_]+/[[:alnum:]\.\-\_]+
@@ -1305,8 +1321,14 @@ PVE::JSONSchema::register_format('pve-poolid', \&verify_poolname);
 sub verify_poolname {
     my ($poolname, $noerr) = @_;
 
-    if ($poolname !~ m/^[A-Za-z0-9\.\-_]+$/) {
+    if (split("/", $poolname) > 3) {
+       die "pool name '$poolname' nested too deeply (max levels = 3)\n" if !$noerr;
+
+       return undef;
+    }
 
+    # also adapt check_path above if changed!
+    if ($poolname !~ m!^[A-Za-z0-9\.\-_]+(?:/[A-Za-z0-9\.\-_]+){0,2}$!) {
        die "pool name '$poolname' contains invalid characters\n" if !$noerr;
 
        return undef;
@@ -1510,7 +1532,21 @@ sub parse_user_config {
            }
 
            # make sure to add the pool (even if there are no members)
-           $cfg->{pools}->{$pool} = { vms => {}, storage => {} } if !$cfg->{pools}->{$pool};
+           $cfg->{pools}->{$pool} = { vms => {}, storage => {}, pools => {} }
+               if !$cfg->{pools}->{$pool};
+
+           if ($pool =~ m!/!) {
+               my $curr = $pool;
+               while ($curr =~ m!^(.+)/[^/]+$!) {
+                   # ensure nested pool info is correctly recorded
+                   my $parent = $1;
+                   $cfg->{pools}->{$curr}->{parent} = $parent;
+                   $cfg->{pools}->{$parent} = { vms => {}, storage => {}, pools => {} }
+                       if !$cfg->{pools}->{$parent};
+                   $cfg->{pools}->{$parent}->{pools}->{$curr} = 1;
+                   $curr = $parent;
+               }
+           }
 
            $cfg->{pools}->{$pool}->{comment} = PVE::Tools::decode_text($comment) if $comment;
 
@@ -1998,15 +2034,11 @@ sub user_get_tfa : prototype($$$) {
     $realm_tfa = PVE::Auth::Plugin::parse_tfa_config($realm_tfa)
        if $realm_tfa;
 
-    if (!$keys) {
-       return if !$realm_tfa;
-       die "missing required 2nd keys\n";
-    }
-
     my $tfa_cfg = cfs_read_file('priv/tfa.cfg');
     if (defined($keys) && $keys !~ /^x(?:!.*)$/) {
        add_old_keys_to_realm_tfa($username, $tfa_cfg, $realm_tfa, $keys);
     }
+
     return ($tfa_cfg, $realm_tfa);
 }