X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=PVE%2FAPI2%2FAccessControl.pm;h=8b053dc821a821222f6236cf26d4d78c412e8dbd;hb=a1281512d0e3cfafb0baa22cde1e7e6a13c52448;hp=9d2da8daaa80e1c53528ccd611e49ecd6f436677;hpb=8967f86f6f38e86cef1e11b5f508f7eb6f426f8d;p=pve-access-control.git diff --git a/PVE/API2/AccessControl.pm b/PVE/API2/AccessControl.pm index 9d2da8d..8b053dc 100644 --- a/PVE/API2/AccessControl.pm +++ b/PVE/API2/AccessControl.pm @@ -6,11 +6,11 @@ use warnings; use JSON; use MIME::Base64; -use PVE::Exception qw(raise raise_perm_exc); +use PVE::Exception qw(raise raise_perm_exc raise_param_exc); use PVE::SafeSyslog; use PVE::RPCEnvironment; use PVE::Cluster qw(cfs_read_file); -use PVE::Corosync; +use PVE::DataCenterConfig; use PVE::RESTHandler; use PVE::AccessControl; use PVE::JSONSchema qw(get_standard_option); @@ -32,36 +32,36 @@ eval { use base qw(PVE::RESTHandler); __PACKAGE__->register_method ({ - subclass => "PVE::API2::User", + subclass => "PVE::API2::User", path => 'users', }); __PACKAGE__->register_method ({ - subclass => "PVE::API2::Group", + subclass => "PVE::API2::Group", path => 'groups', }); __PACKAGE__->register_method ({ - subclass => "PVE::API2::Role", + subclass => "PVE::API2::Role", path => 'roles', }); __PACKAGE__->register_method ({ - subclass => "PVE::API2::ACL", + subclass => "PVE::API2::ACL", path => 'acl', }); __PACKAGE__->register_method ({ - subclass => "PVE::API2::Domains", + subclass => "PVE::API2::Domains", path => 'domains', }); __PACKAGE__->register_method ({ - name => 'index', - path => '', + name => 'index', + path => '', method => 'GET', description => "Directory index.", - permissions => { + permissions => { user => 'all', }, parameters => { @@ -80,7 +80,7 @@ __PACKAGE__->register_method ({ }, code => sub { my ($param) = @_; - + my $res = []; my $ma = __PACKAGE__->method_attributes(); @@ -176,11 +176,12 @@ my $compute_api_permission = sub { access => qr/(User|Group)\.|Permissions\.Modify/, storage => qr/Datastore\.|Permissions\.Modify/, nodes => qr/Sys\.|Permissions\.Modify/, - dc => qr/Sys\.Audit/, + sdn => qr/SDN\./, + dc => qr/Sys\.Audit|SDN\./, }; map { $res->{$_} = {} } keys %$priv_re_map; - my $required_paths = ['/', '/nodes', '/access/groups', '/vms', '/storage']; + my $required_paths = ['/', '/nodes', '/access/groups', '/vms', '/storage', '/sdn']; my $checked_paths = {}; foreach my $path (@$required_paths, keys %{$usercfg->{acl}}) { @@ -214,8 +215,8 @@ my $compute_api_permission = sub { }; __PACKAGE__->register_method ({ - name => 'get_ticket', - path => 'ticket', + name => 'get_ticket', + path => 'ticket', method => 'GET', permissions => { user => 'world' }, description => "Dummy. Useful for formatters which want to provide a login page.", @@ -224,16 +225,17 @@ __PACKAGE__->register_method ({ }, returns => { type => "null" }, code => sub { return undef; }}); - + __PACKAGE__->register_method ({ - name => 'create_ticket', - path => 'ticket', + name => 'create_ticket', + path => 'ticket', method => 'POST', - permissions => { + permissions => { description => "You need to pass valid credientials.", - user => 'world' + user => 'world' }, protected => 1, # else we can't access shadow files + allowtoken => 0, # we don't want tokens to create tickets description => "Create or verify authentication ticket.", parameters => { additionalProperties => 0, @@ -249,7 +251,7 @@ __PACKAGE__->register_method ({ optional => 1, completion => \&PVE::AccessControl::complete_realm, }), - password => { + password => { description => "The secret password. This can also be a valid ticket.", type => 'string', }, @@ -265,7 +267,7 @@ __PACKAGE__->register_method ({ optional => 1, maxLength => 64, }, - privs => { + privs => { description => "Verify ticket, and check if user have access 'privs' on 'path'", type => 'string' , format => 'pve-priv-list', requires => 'path', @@ -286,7 +288,7 @@ __PACKAGE__->register_method ({ }, code => sub { my ($param) = @_; - + my $username = $param->{username}; $username .= "\@$param->{realm}" if $param->{realm}; @@ -314,17 +316,9 @@ __PACKAGE__->register_method ({ $res->{cap} = &$compute_api_permission($rpcenv, $username) if !defined($res->{NeedTFA}); - if (PVE::Corosync::check_conf_exists(1)) { - if ($rpcenv->check($username, '/', ['Sys.Audit'], 1)) { - eval { - my $conf = cfs_read_file('corosync.conf'); - my $totem = PVE::Corosync::totem_config($conf); - if ($totem->{cluster_name}) { - $res->{clustername} = $totem->{cluster_name}; - } - }; - warn "$@\n" if $@; - } + my $clinfo = PVE::Cluster::get_clinfo(); + if ($clinfo->{cluster}->{name} && $rpcenv->check($username, '/', ['Sys.Audit'], 1)) { + $res->{clustername} = $clinfo->{cluster}->{name}; } PVE::Cluster::log_msg('info', 'root@pam', "successful auth for user '$username'"); @@ -334,11 +328,11 @@ __PACKAGE__->register_method ({ __PACKAGE__->register_method ({ name => 'change_password', - path => 'password', + path => 'password', method => 'PUT', - permissions => { + permissions => { description => "Each user is allowed to change his own password. A user can change the password of another user if he has 'Realm.AllocateUser' (on the realm of user ) and 'User.Modify' permission on /access/groups/ on a group where user is member of.", - check => [ 'or', + check => [ 'or', ['userid-param', 'self'], [ 'and', [ 'userid-param', 'Realm.AllocateUser'], @@ -347,15 +341,16 @@ __PACKAGE__->register_method ({ ], }, protected => 1, # else we can't access shadow files + allowtoken => 0, # we don't want tokens to change the regular user password description => "Change user password.", parameters => { additionalProperties => 0, properties => { userid => get_standard_option('userid-completed'), - password => { + password => { description => "The new password.", type => 'string', - minLength => 5, + minLength => 5, maxLength => 64, }, } @@ -398,7 +393,6 @@ sub get_u2f_config() { my $dc = cfs_read_file('datacenter.cfg'); my $u2f = $dc->{u2f}; die "u2f not configured in datacenter.cfg\n" if !$u2f; - $u2f = PVE::JSONSchema::parse_property_string($PVE::Cluster::u2f_format, $u2f); return $u2f; } @@ -479,6 +473,7 @@ __PACKAGE__->register_method ({ ], }, protected => 1, # else we can't access shadow files + allowtoken => 0, # we don't want tokens to change the regular user's TFA settings description => "Change user u2f authentication.", parameters => { additionalProperties => 0, @@ -509,9 +504,7 @@ __PACKAGE__->register_method ({ optional => 1, description => 'When adding TOTP, the shared secret value.', type => 'string', - # This is what pve-common's PVE::OTP::oath_verify_otp accepts. - # Should we move this to pve-common's JSONSchema as a named format? - pattern => qr/[A-Z2-7=]{16}|[A-Fa-f0-9]{40}/, + format => 'pve-tfa-secret', }, config => { optional => 1, @@ -543,11 +536,11 @@ __PACKAGE__->register_method ({ # Regular users need to confirm their password to change u2f settings. if ($authuser ne 'root@pam') { - raise_param_exc('password' => 'password is required to modify u2f data') + raise_param_exc({ 'password' => 'password is required to modify u2f data' }) if !defined($password); my $domain_cfg = cfs_read_file('domains.cfg'); my $cfg = $domain_cfg->{ids}->{$realm}; - die "auth domain '$realm' does not exists\n" if !$cfg; + die "auth domain '$realm' does not exist\n" if !$cfg; my $plugin = PVE::Auth::Plugin->lookup($cfg->{type}); $plugin->authenticate_user($cfg, $realm, $ruid, $password); } @@ -575,7 +568,7 @@ __PACKAGE__->register_method ({ return $challenge; } } elsif ($action eq 'confirm') { - raise_param_exc('response' => "confirm action requires the 'response' parameter to be set") + raise_param_exc({ 'response' => "confirm action requires the 'response' parameter to be set" }) if !defined($response); my ($type, $u2fdata) = PVE::AccessControl::user_get_tfa($userid, $realm); @@ -605,6 +598,7 @@ __PACKAGE__->register_method({ method => 'POST', permissions => { user => 'all' }, protected => 1, # else we can't access shadow files + allowtoken => 0, # we don't want tokens to access TFA information description => 'Finish a u2f challenge.', parameters => { additionalProperties => 0, @@ -668,4 +662,60 @@ __PACKAGE__->register_method({ } }}); +__PACKAGE__->register_method({ + name => 'permissions', + path => 'permissions', + method => 'GET', + description => 'Retrieve effective permissions of given user/token.', + permissions => { + description => "Each user/token is allowed to dump their own permissions. A user can dump the permissions of another user if they have 'Sys.Audit' permission on /access.", + user => 'all', + }, + parameters => { + additionalProperties => 0, + properties => { + userid => { + type => 'string', + description => "User ID or full API token ID", + pattern => $PVE::AccessControl::userid_or_token_regex, + optional => 1, + }, + path => get_standard_option('acl-path', { + description => "Only dump this specific path, not the whole tree.", + optional => 1, + }), + }, + }, + returns => { + type => 'object', + description => 'Map of "path" => (Map of "privilege" => "propagate boolean").', + }, + code => sub { + my ($param) = @_; + + my $rpcenv = PVE::RPCEnvironment::get(); + + my $userid = $param->{userid}; + if (defined($userid)) { + $rpcenv->check($rpcenv->get_user(), '/access', ['Sys.Audit']); + } else { + $userid = $rpcenv->get_user(); + } + + my $res; + + if (my $path = $param->{path}) { + my $perms = $rpcenv->permissions($userid, $path); + if ($perms) { + $res = { $path => $perms }; + } else { + $res = {}; + } + } else { + $res = $rpcenv->get_effective_permissions($userid); + } + + return $res; + }}); + 1;