use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file);
use PVE::JSONSchema qw(register_standard_option get_standard_option);
+use PVE::RS::TFA;
+
use PVE::Auth::Plugin;
use PVE::Auth::AD;
use PVE::Auth::LDAP;
use PVE::Auth::PVE;
use PVE::Auth::PAM;
+use PVE::Auth::OpenId;
# load and initialize all plugins
PVE::Auth::LDAP->register();
PVE::Auth::PVE->register();
PVE::Auth::PAM->register();
+PVE::Auth::OpenId->register();
PVE::Auth::Plugin->init();
# $authdir must be writable by root only!
Crypt::OpenSSL::RSA->import_random_seed();
-cfs_register_file('user.cfg',
- \&parse_user_config,
- \&write_user_config);
-cfs_register_file('priv/tfa.cfg',
- \&parse_priv_tfa_config,
- \&write_priv_tfa_config);
+cfs_register_file('user.cfg', \&parse_user_config, \&write_user_config);
+cfs_register_file('priv/tfa.cfg', \&parse_priv_tfa_config, \&write_priv_tfa_config);
sub verify_username {
PVE::Auth::Plugin::verify_username(@_);
check_user_enabled($usercfg, $username);
check_token_exist($usercfg, $username, $token);
- my $ctime = time();
-
my $user = $usercfg->{users}->{$username};
- die "account expired\n" if $user->{expire} && ($user->{expire} < $ctime);
-
my $token_info = $user->{tokens}->{$token};
+
+ my $ctime = time();
die "token expired\n" if $token_info->{expire} && ($token_info->{expire} < $ctime);
die "invalid token value!\n" if !PVE::Cluster::verify_token($tokenid, $value);
die "user '$username' is disabled\n" if !$noerr;
+ my $ctime = time();
+ my $expire = $usercfg->{users}->{$username}->{expire};
+
+ die "account expired\n" if $expire && ($expire < $ctime);
+
return undef;
}
check_user_enabled($usercfg, $username);
- my $ctime = time();
- my $expire = $usercfg->{users}->{$username}->{expire};
-
- die "account expired\n" if $expire && ($expire < $ctime);
-
my $domain_cfg = cfs_read_file('domains.cfg');
my $cfg = $domain_cfg->{ids}->{$realm};
admin => [
'Pool.Allocate', # create/delete pools
],
- user => [],
- audit => [],
+ user => [
+ 'Pool.Audit',
+ ],
+ audit => [
+ 'Pool.Audit',
+ ],
},
};
/
|/access
|/access/groups
+ |/access/groups/[[:alnum:]\.\-\_]+
|/access/realm
+ |/access/realm/[[:alnum:]\.\-\_]+
|/nodes
|/nodes/[[:alnum:]\.\-\_]+
|/pool
|/pool/[[:alnum:]\.\-\_]+
|/sdn
+ |/sdn/zones/[[:alnum:]\.\-\_]+
+ |/sdn/vnets/[[:alnum:]\.\-\_]+
|/storage
|/storage/[[:alnum:]\.\-\_]+
|/vms
return $data;
}
-# The TFA configuration in priv/tfa.cfg format contains one line per user of
-# the form:
-# USER:TYPE:DATA
-# DATA is a base64 encoded json string and its format depends on the type.
+# Creates a `PVE::RS::TFA` instance from the raw config data.
+# Its contained hash will also support the legacy functionality.
sub parse_priv_tfa_config {
my ($filename, $raw) = @_;
- my $users = {};
- my $cfg = { users => $users };
-
$raw = '' if !defined($raw);
- while ($raw =~ /^\s*(.+?)\s*$/gm) {
- my $line = $1;
- my ($user, $type, $data) = split(/:/, $line, 3);
+ my $cfg = PVE::RS::TFA->new($raw);
+ # Purge invalid users:
+ foreach my $user ($cfg->users()->@*) {
my (undef, undef, $realm) = PVE::Auth::Plugin::verify_username($user, 1);
if (!$realm) {
warn "user tfa config - ignore user '$user' - invalid user name\n";
- next;
+ $cfg->remove_user($user);
}
-
- $data = decode_json(decode_base64($data));
-
- $users->{$user} = {
- type => $type,
- data => $data,
- };
}
return $cfg;
sub write_priv_tfa_config {
my ($filename, $cfg) = @_;
- my $output = '';
-
- my $users = $cfg->{users};
- foreach my $user (sort keys %$users) {
- my $info = $users->{$user};
- next if !%$info; # skip empty entries
-
- $info = {%$info}; # copy to verify contents:
-
- my $type = delete $info->{type};
- my $data = delete $info->{data};
-
- if (my @keys = keys %$info) {
- die "invalid keys in TFA config for user $user: " . join(', ', @keys) . "\n";
- }
-
- $data = encode_base64(encode_json($data), '');
- $output .= "${user}:${type}:${data}\n";
- }
-
- return $output;
+ # FIXME: Only allow this if the complete cluster has been upgraded to understand the json
+ # config format.
+ return $cfg->write();
}
sub roles {
}
my $user_cfg = $cached_usercfg || cfs_read_file('user.cfg');
- my $user = $user_cfg->{users}->{$userid}
- or die "user '$userid' not found\n";
+ my $user = $user_cfg->{users}->{$userid};
my $domain_cfg = $cached_domaincfg || cfs_read_file('domains.cfg');
my $realm_cfg = $domain_cfg->{ids}->{$realm};
$realm_tfa = PVE::Auth::Plugin::parse_tfa_config($realm_tfa);
# If the realm has a TFA setting, we're only allowed to use that.
if (defined($data)) {
+ die "user '$userid' not found\n" if !defined($user);
my $required_type = $realm_tfa->{type};
if ($required_type ne $type) {
die "realm '$realm' only allows TFA of type '$required_type\n";
# realm-configured tfa always uses a simple key list, so use the user.cfg
$user->{keys} = $data->{keys};
} else {
- die "realm '$realm' does not allow removing the 2nd factor\n";
+ # TFA is enforce by realm, only allow deletion if the whole user gets delete
+ die "realm '$realm' does not allow removing the 2nd factor\n" if defined($user);
}
} else {
+ die "user '$userid' not found\n" if !defined($user) && defined($data);
# Without a realm-enforced TFA setting the user can add a u2f or totp entry by themselves.
# The 'yubico' type requires yubico server settings, which have to be configured on the
# realm, so this is not supported here:
delete $tfa_cfg->{users}->{$userid};
cfs_write_file('priv/tfa.cfg', $tfa_cfg);
- delete $user->{keys};
+ delete $user->{keys} if defined($user);
}
- cfs_write_file('user.cfg', $user_cfg);
+ cfs_write_file('user.cfg', $user_cfg) if defined($user);
}
sub user_get_tfa {