]> git.proxmox.com Git - pve-access-control.git/blobdiff - PVE/AccessControl.pm
api/users: catch existing user also on case insensitive realm
[pve-access-control.git] / PVE / AccessControl.pm
index 5e1185f9909efdd7d796c5f8f012c3d8abc0552b..8b5be1e03c570eab4c381c75ccac3111ba19ffdd 100644 (file)
@@ -48,6 +48,7 @@ my $pve_auth_key_files = {
 my $pve_auth_key_cache = {};
 
 my $ticket_lifetime = 3600 * 2; # 2 hours
+my $auth_graceperiod = 60 * 5; # 5 minutes
 my $authkey_lifetime = 3600 * 24; # rotate every 24 hours
 
 Crypt::OpenSSL::RSA->import_random_seed();
@@ -148,9 +149,22 @@ sub check_authkey {
        warn "auth key pair missing, generating new one..\n"  if !$quiet;
        return 0;
     } else {
-       if (time() - $mtime >= $authkey_lifetime) {
+       my $now = time();
+       if ($now - $mtime >= $authkey_lifetime) {
            warn "auth key pair too old, rotating..\n" if !$quiet;;
            return 0;
+       } elsif ($mtime > $now + $auth_graceperiod) {
+           # a nodes RTC had a time set in the future during key generation -> ticket
+           # validity is clamped to 0+5 min grace period until now >= mtime again
+           my (undef, $old_mtime) = get_pubkey(1);
+           if ($old_mtime && $mtime >= $old_mtime && $mtime - $old_mtime < $ticket_lifetime) {
+               warn "auth key pair generated in the future (key $mtime > host $now),"
+                   ." but old key still exists and in valid grace period so avoid automatic"
+                   ." fixup. Cluster time not in sync?\n" if !$quiet;
+               return 1;
+           }
+           warn "auth key pair generated in the future (key $mtime > host $now), rotating..\n" if !$quiet;
+           return 0;
        } else {
            warn "auth key new enough, skipping rotation\n" if !$quiet;;
            return 1;
@@ -292,7 +306,7 @@ sub verify_csrf_prevention_token {
     }
 
     return PVE::Ticket::verify_csrf_prevention_token(
-       $secret, $username, $token, -300, $ticket_lifetime, $noerr);
+       $secret, $username, $token, -$auth_graceperiod, $ticket_lifetime, $noerr);
 }
 
 my $get_ticket_age_range = sub {
@@ -301,12 +315,12 @@ my $get_ticket_age_range = sub {
     my $key_age = $now - $mtime;
     $key_age = 0 if $key_age < 0;
 
-    my $min = -300;
+    my $min = -$auth_graceperiod;
     my $max = $ticket_lifetime;
 
     if ($rotated) {
        # ticket creation after rotation is not allowed
-       $min = $key_age - 300;
+       $min = $key_age - $auth_graceperiod;
     } else {
        if ($key_age > $authkey_lifetime && $authkey_lifetime > 0) {
            if (PVE::Cluster::check_cfs_quorum(1)) {
@@ -317,7 +331,7 @@ my $get_ticket_age_range = sub {
            }
        }
 
-       $max = $key_age + 300 if $key_age < $ticket_lifetime;
+       $max = $key_age + $auth_graceperiod if $key_age < $ticket_lifetime;
     }
 
     return undef if $min > $ticket_lifetime;
@@ -741,6 +755,7 @@ my $privgroups = {
        ],
        user => [
            'VM.Config.CDROM', # change CDROM media
+           'VM.Config.Cloudinit',
            'VM.Console',
            'VM.Backup',
            'VM.PowerMgmt',
@@ -876,6 +891,28 @@ sub add_role_privs {
     }
 }
 
+sub lookup_username {
+    my ($username, $noerr) = @_;
+
+    $username =~ m!^(${PVE::Auth::Plugin::user_regex})\@(${PVE::Auth::Plugin::realm_regex})$!;
+
+    my $realm = $2;
+    my $domain_cfg = cfs_read_file("domains.cfg");
+    my $casesensitive = $domain_cfg->{ids}->{$realm}->{'case-sensitive'} // 1;
+    my $usercfg = cfs_read_file('user.cfg');
+
+    if (!$casesensitive) {
+       my @matches = grep { lc $username eq lc $_ } (keys %{$usercfg->{users}});
+
+       die "ambiguous case insensitive match of username '$username', cannot safely grant access!\n"
+           if scalar @matches > 1 && !$noerr;
+
+       return $matches[0]
+    }
+
+    return $username;
+}
+
 sub normalize_path {
     my $path = shift;
 
@@ -1041,10 +1078,10 @@ sub parse_user_config {
 
                if ($cfg->{users}->{$user}) { # user exists
                    $cfg->{users}->{$user}->{groups}->{$group} = 1;
-                   $cfg->{groups}->{$group}->{users}->{$user} = 1;
                } else {
                    warn "user config - ignore invalid group member '$user'\n";
                }
+               $cfg->{groups}->{$group}->{users}->{$user} = 1;
            }
 
        } elsif ($et eq 'role') {
@@ -1088,17 +1125,15 @@ sub parse_user_config {
                        my ($group) = $ug =~ m/^@(\S+)$/;
 
                        if ($group && verify_groupname($group, 1)) {
-                           if ($cfg->{groups}->{$group}) { # group exists
-                               $cfg->{acl}->{$path}->{groups}->{$group}->{$role} = $propagate;
-                           } else {
+                           if (!$cfg->{groups}->{$group}) { # group does not exist
                                warn "user config - ignore invalid acl group '$group'\n";
                            }
+                           $cfg->{acl}->{$path}->{groups}->{$group}->{$role} = $propagate;
                        } elsif (PVE::Auth::Plugin::verify_username($ug, 1)) {
-                           if ($cfg->{users}->{$ug}) { # user exists
-                               $cfg->{acl}->{$path}->{users}->{$ug}->{$role} = $propagate;
-                           } else {
+                           if (!$cfg->{users}->{$ug}) { # user does not exist
                                warn "user config - ignore invalid acl member '$ug'\n";
                            }
+                           $cfg->{acl}->{$path}->{users}->{$ug}->{$role} = $propagate;
                        } elsif (my ($user, $token) = split_tokenid($ug, 1)) {
                            if (check_token_exist($cfg, $user, $token, 1)) {
                                $cfg->{acl}->{$path}->{tokens}->{$ug}->{$role} = $propagate;