mirror of
git://git.proxmox.com/git/pve-access-control.git
synced 2025-01-03 01:17:55 +03:00
iimported from svn 'pve-access-control/trunk'
This commit is contained in:
commit
2c3a6c0aaa
457
ChangeLog
Normal file
457
ChangeLog
Normal file
@ -0,0 +1,457 @@
|
||||
2011-08-15 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* PVE/AccessControl.pm (parse_user_config): fix parser for files
|
||||
without newline at eof
|
||||
(parse_shadow_passwd): fix parser for files without newline at eof
|
||||
(parse_domains): fix parser for files without newline at eof
|
||||
|
||||
2011-08-01 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* PVE/AccessControl.pm (lock_*): remove $parent in calls to
|
||||
cfs_lock_file()
|
||||
|
||||
2011-07-22 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* PVE/API2/Domains.pm (create): use lower case: s/AD/ad/ and
|
||||
s/LDAP/ldap/
|
||||
|
||||
* PVE/AccessControl.pm (write_domains): use lc($type)
|
||||
|
||||
2011-07-14 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* control.in (Depends): remove depend on liburi-perl (code moved
|
||||
to pve-common)
|
||||
|
||||
2011-07-05 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* PVE/API2/User.pm (create_user): add -enable parameter
|
||||
|
||||
* PVE/API2/User.pm (update_user): use -enable instead of
|
||||
-lock/-unlock
|
||||
|
||||
2011-06-27 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* PVE/AccessControl.pm (normalize_path): allow '-' in path
|
||||
|
||||
2011-05-30 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* PVE/AccessControl.pm (assemble_csrf_prevention_token): CSRF
|
||||
token may not depend on cookie, because cookie can be updated from
|
||||
other window.
|
||||
|
||||
2011-03-30 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* PVE/API2/AccessControl.pm (create_ticket): also return user name
|
||||
|
||||
2011-03-24 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* PVE/AccessControl.pm (verify_csrf_prevention_token): add CSRF
|
||||
prevention code
|
||||
|
||||
2011-03-23 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* PVE/RPCEnvironment.pm (active_workers): simple log rotation when
|
||||
file is bigger that 50KB
|
||||
|
||||
2011-03-22 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* PVE/RPCEnvironment.pm (set_result_count): a way to set the total
|
||||
number of results - we use that for the ExtJS paging grid.
|
||||
|
||||
2011-03-21 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* PVE/RPCEnvironment.pm (active_workers): immediately move finished
|
||||
task to the index file.
|
||||
|
||||
2011-03-17 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* PVE/RPCEnvironment.pm (active_workers): update/get worker list
|
||||
|
||||
2011-03-16 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* PVE/RPCEnvironment.pm (fork_worker): add code to simulate running
|
||||
in foreground (cli).
|
||||
|
||||
2011-02-24 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* PVE/AccessControl.pm (roles): fix group permission propagation
|
||||
|
||||
* PVE/API2/ACL.pm: cleanup API - use '-users' and '-gropus'
|
||||
instead of '-uglist'
|
||||
|
||||
2011-02-23 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* PVE/API2/AccessControl.pm (create_ticket): moved code from REST.pm
|
||||
|
||||
2011-02-22 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* PVE/AccessControl.pm: make 'domains.cfg' readable by www-data,
|
||||
add 'default' attribute.
|
||||
|
||||
* PVE/AccessControl.pm: realm is now part of the username.
|
||||
Example: 'userid@realm'
|
||||
(valid_attributes): add 'domain, port, secure' attributes for AD.
|
||||
(parse_domains): add attribute 'secure' (replace LDAPS type),
|
||||
|
||||
* PVE/AccessControl.pm (parse_user_config): add firstname/lastname
|
||||
and email fields.
|
||||
|
||||
2011-02-21 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* PVE/API2/Group.pm (update_group): implement modgroup (set
|
||||
comment)
|
||||
|
||||
2011-02-18 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* PVE/AccessControl.pm (create_roles): try to create a predefined
|
||||
set of roles automatically.
|
||||
|
||||
2011-02-17 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* PVE/API2/Domains.pm: new API to for domains.cfg
|
||||
|
||||
* PVE/AccessControl.pm (authenticate_user_domain): added a 'domid'
|
||||
attribute to users. This references an entry in the domain
|
||||
config. This is simpler than the previous domain search
|
||||
algorithm.
|
||||
|
||||
* PVE/API2/User.pm: save domid, name, comment and expire time for
|
||||
user entries.
|
||||
|
||||
* PVE/AccessControl.pm (authenticate_user): check for expired
|
||||
accounts
|
||||
|
||||
* control.in (Depends): depend on liburi-perl (we use URI::Escape
|
||||
to encode text in our config files).
|
||||
|
||||
* PVE/AccessControl.pm (enable_user, disable_user): removed
|
||||
clumsy methods, not needed.
|
||||
|
||||
2011-02-16 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* README (privileges): Changes set of privileges. We try to be as
|
||||
simple as possible. We can refinen them in future.
|
||||
|
||||
* PVE/ACLCache.pm: deleted - moved code into RPCEnvironment.
|
||||
|
||||
2011-02-15 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* PVE/AccessControl.pm (verify_username): restrict user names to
|
||||
64 charachters. Add new priviledges Sys.PowerOff, Sys.Console and
|
||||
Sys.Syslog
|
||||
|
||||
* PVE/ACLCache.pm: move code into new file.
|
||||
|
||||
* test/perm-test1.pl: modified to use new PVE::ACLCache class.
|
||||
|
||||
* PVE/AccessControl.pm: add new class PVE::ACLCache (speed up ACL
|
||||
checks)
|
||||
|
||||
2011-01-27 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* pveum (auth): remove auth method - we do not use it any
|
||||
longer, comment out ability to pass password via environment
|
||||
variable.
|
||||
|
||||
* PVE/AccessControl.pm (check_permissions): new helper to check
|
||||
permissions.
|
||||
|
||||
2011-01-21 root <root@maui.maurer-it.com>
|
||||
|
||||
* PVE/AccessControl.pm: register a JSONSchema standard option for
|
||||
'userid'.
|
||||
|
||||
* pveum: allow to pass passwords with environment variable
|
||||
PVE_PW_TICKET
|
||||
* pveum (auth): new method to verify credentials/privileges (used
|
||||
by our kvm patches and vncterm)
|
||||
|
||||
2011-01-12 root <root@maui.maurer-it.com>
|
||||
|
||||
* PVE/AccessControl.pm: use new PVE::Cluster class and read data
|
||||
from cluster filesystem (instead of local filesystem).
|
||||
|
||||
2011-01-11 root <root@maui.maurer-it.com>
|
||||
|
||||
* control.in (Depends): depend on new pve-cluster package
|
||||
|
||||
* PVE/AccessControl.pm (read_pubkey, read_privkey): inotify does
|
||||
not work on the cluster filesystem, so I removed that code. Also
|
||||
moved lock files to /var/lock/pve-manager (cluster filesystem does
|
||||
not support locks - we need to do cluster wide locks later)
|
||||
|
||||
2010-09-14 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* PVE/API2/AccessControl.pm: moved from pve-manager
|
||||
|
||||
* PVE/: create correct directory hierarchy
|
||||
|
||||
* Makefile (install): use 'verifyapi'
|
||||
|
||||
* pveum: add verifyapi
|
||||
|
||||
2010-08-25 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* pveum: use new PVE::CLIHandler
|
||||
|
||||
2010-08-24 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* pveum: use new PVE::RPCEnvironment
|
||||
|
||||
* *.pm: remove $conn parameter everywhere
|
||||
|
||||
2010-08-16 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* AccessControl.pm (lock_user_config): add call to die, remove
|
||||
@param - we do not need that here
|
||||
(lock_shadow_config): add call to die, remove @param
|
||||
|
||||
* *.pm: remove $resp parameter everywhere.
|
||||
|
||||
* AccessControl.pm (verify_username): add test for username
|
||||
length (at least 3 characters)
|
||||
|
||||
2010-08-13 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* User.pm: use new 'format' property in schema
|
||||
|
||||
* ACL.pm: use new 'format' property in schema, remove redundant
|
||||
calls to verify_XXX calls.
|
||||
|
||||
* Role.pm: use new 'format' property in schema, remove redundant
|
||||
calls to verify_XXX calls.
|
||||
|
||||
* Group.pm: use new 'format' property in schema, remove redundant
|
||||
calls to verify_XXX calls.
|
||||
|
||||
* AccessControl.pm (modify_acl): strict error checking - use 'die'
|
||||
instead of 'warn', moved to ACL.pm
|
||||
(verify_username): fix serious bug
|
||||
|
||||
2010-08-12 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* Group.pm: use the new RESTHandler for API methods
|
||||
|
||||
* Role.pm: use the new RESTHandler for API methods
|
||||
|
||||
* AccessControl.pm (add_group): moved to Group.pm
|
||||
(delete_group): moved to Group.pm
|
||||
(delete_role): moved to Role.pm
|
||||
(modify_role): moved to Role.pm
|
||||
|
||||
* User.pm: strict error checking - use 'die' instead of 'warn'
|
||||
|
||||
* User.pm (delete_user): raise error when user does not exist.
|
||||
|
||||
* Group.pm (delete_group): raise error when group does not exist.
|
||||
|
||||
* pveum: use the new
|
||||
RESTHandler (PVE::API2::User->cli_handler()). That way we have
|
||||
automatic command line argument parsing.
|
||||
|
||||
* User.pm: use the new RESTHandler for API methods. Those methods
|
||||
are automatically exposed with the API Server (pve-manager), and
|
||||
we can use them in the command line tools.
|
||||
|
||||
* AccessControl.pm (modify_user, delete_user): moved to User.pm
|
||||
|
||||
2010-08-10 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* control.in (Depends): depend on libpve-common-perl
|
||||
|
||||
* AccessControl.pm: initialize Crypt::OpenSSL::RSA with
|
||||
import_random_seed(), else I get a 'Segmentation fault' when
|
||||
creating tickets ("pveum ticket <testuser>").
|
||||
|
||||
* AccessControl.pm: Moved utilities to new PVE::Tools
|
||||
module (pve-common), use new PVE::INotify to read/write config files.
|
||||
|
||||
* AccessControl.pm (parse_domains): ignore case (always convert
|
||||
type to lower case), fix bug from Seth and test for 'ldaps'.
|
||||
(file_set_contents): use O_WRONLY|O_CREAT instead of 'w' - else
|
||||
perm gets ignored.
|
||||
|
||||
2010-08-09 Seth Lauzon <seth.lauzon@gmail.com>
|
||||
|
||||
* AccessControl.pm (authenticate_user_ldap): changed the bind function
|
||||
for LDAP to allow for secure connection
|
||||
|
||||
2010-07-21 Seth Lauzon <seth.lauzon@gmail.com>
|
||||
|
||||
* AccessControl.pm (parse_domains): require base_dn for LDAP domains
|
||||
(valid_attributes): renamed from valid_params to maintain conformity
|
||||
|
||||
2010-07-19 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* AccessControl.pm (authenticate_user_domain): always add timeout
|
||||
after failed auth
|
||||
(file_set_contents): correctly emit exception if print/close fails
|
||||
|
||||
2010-07-19 Seth Lauzon <seth.lauzon@gmail.com>
|
||||
|
||||
* AccessControl.pm: fixed timeout for ldap/AD errors and reduced to two seconds
|
||||
|
||||
* AccessControl.pm: modified LDAP authentication to a two step bind method
|
||||
|
||||
2010-07-16 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* AccessControl.pm (authenticate_user_domain): catch special
|
||||
case ($domain eq '')
|
||||
(parse_domains): fix various bugs, allow spaces between domains,
|
||||
skip duplicate parameters
|
||||
|
||||
2010-07-16 Seth Lauzon <seth.lauzon@gmail.com>
|
||||
|
||||
* AccessControl.pm (parse_domains): borrowed code from Storage.pm to make it
|
||||
less fragile to syntax errors in the domains.cfg file
|
||||
|
||||
* AccessControl.pm: implemented LDAP authentication
|
||||
|
||||
* AccessControl.pm: added four second timeout on authentication failure for
|
||||
user_authentication_ldap and user_authentication_ad
|
||||
|
||||
2010-07-14 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* AccessControl.pm (ldap_bind): rename to authenticate_user_ad (AD
|
||||
only)
|
||||
(load_domains_config): return a reference to an array (not the
|
||||
array itself)
|
||||
(parse_config): return a reference to an array (not the array
|
||||
itself)
|
||||
(authenticate_user_domain): restructure code - this is no the
|
||||
centralized interface for authenticationn
|
||||
(authenticate_user_domain): add 'shadow' and 'PAM' default entries
|
||||
if there is no configuration for them in domain.cfg
|
||||
(authenticate_user_shadow): renamed from authenticate_user_pve
|
||||
|
||||
* control.in (Depends): add libnet-ldap-perl
|
||||
|
||||
2010-07-14 Seth Lauzon <seth.lauzon@gmail.com>A
|
||||
|
||||
* AccessControl.pm: implemented Active Directory authentication
|
||||
|
||||
2010-07-09 Seth Lauzon <seth.lauzon@gmail.com>
|
||||
|
||||
* AccessControl.pm (modify_acl): check if role exists
|
||||
|
||||
2010-07-08 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* pveum (print_usage): improve usage text.
|
||||
|
||||
2010-07-08 Seth Lauzon <seth.lauzon@gmail.com>
|
||||
|
||||
* AccessControl.pm: modify/delete ACL functionality
|
||||
|
||||
* pveum (aclmod): Add/Modify ACL
|
||||
(acldel): Delete ACL
|
||||
|
||||
2010-07-07 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* AccessControl.pm: implemented shadowauthentication (add/modify/delete/verify)
|
||||
with file locking (Seth)
|
||||
(encrypt_pw): use SHA256 to crypt passwords
|
||||
(save_shadow_config): change mode to 0600, store to /etc/pve/auth/shadow.cfg
|
||||
(parse_shadow): simplify code - there is no need to trim strings. Instead check for
|
||||
correct format.
|
||||
|
||||
* test/auth-test.pl: program for testing authentication methods (Seth)
|
||||
|
||||
* pveum (read_password): added confirm password
|
||||
|
||||
2010-07-05 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* AccessControl.pm (modify_user): remove call to change_password()
|
||||
- not neccessary at all (Seth)
|
||||
* AccessControl.pm: cleanup - remove space in function calls(Seth)
|
||||
|
||||
2010-07-02 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* AccessControl.pm (lock_user_config): renamed from lock_config,
|
||||
because we will have more then one config file (auth.conf, shadow
|
||||
password, ...)
|
||||
(modify_user): check for exceptions after lock_user_config()
|
||||
(delete_user): check for exceptions after lock_user_config(),
|
||||
raise invalid characters exception
|
||||
(delete_group): check for exceptions after lock_user_config(),
|
||||
raise invalid characters exception
|
||||
(modify_role): check for exceptions after lock_user_config()
|
||||
(delete_role): check for exceptions after lock_user_config(),
|
||||
raise invalid characters exception
|
||||
(verify_username): add $noerr parameter, raise exeption if
|
||||
user name contain invalid characters and $noerr is not set
|
||||
(verify_groupname): add $noerr parameter, raise exeption if
|
||||
group name contain invalid characters and $noerr is not set
|
||||
(verify_rolename): add $noerr parameter, raise exeption if
|
||||
role name contain invalid characters and $noerr is not set
|
||||
|
||||
2010-07-01 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* AccessControl.pm: implemented file locking functionality for all
|
||||
processes that make modifications to configuration file (Seth) -
|
||||
code for lock_file() was copied from QemuServer.pm.
|
||||
|
||||
2010-06-29 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* pveum: new roleadd/rolemod/roledel (Seth)
|
||||
|
||||
* AccessControl.pm (modify_role): create role and modify privileges (Seth)
|
||||
|
||||
* AccessControl.pm (delete_role): delete role functionality (Seth)
|
||||
|
||||
2010-06-28 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* pveum: new groupadd/groupdel (patch from Seth)
|
||||
|
||||
* AccessControl.pm (add_user): moved functionality to modify_user and
|
||||
removed subroutine (Seth)
|
||||
|
||||
* pveum: useradd command no longer requires a password and now uses
|
||||
modify_user (Seth)
|
||||
|
||||
2010-06-25 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* AccessControl.pm (modify_user): include patch from Seth
|
||||
|
||||
2010-06-24 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* test/perm-test1.pl (check_permission): a first regression test
|
||||
|
||||
* test/user.cfg.ex1: add another example - for use by regression
|
||||
tests
|
||||
|
||||
* test/dump-perm.pl: print permission as nice list, add ability to
|
||||
specify usr.cfg file
|
||||
|
||||
2010-06-23 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* pveum: implement some simple functions (add user, create ticket)
|
||||
|
||||
* pveum-pl: rename to pveum
|
||||
|
||||
* pveum.c: remove suexec code - we will use a daemon instead
|
||||
|
||||
* pvesh: removed (dead code)
|
||||
|
||||
* test/dump-perm.pl: simple script to dump permissions
|
||||
|
||||
* test/: created new directory for test skripts
|
||||
|
||||
* test/dump-users.pl: simple script to dump user table
|
||||
|
||||
2010-06-22 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* AccessControl.pm (add_user): Updated "valid_privs" with new
|
||||
permissions from readme (Seth)
|
||||
|
||||
2010-06-21 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* copyright: change license to AGPL
|
||||
|
||||
2010-03-17 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* pveum-pl: move all priviledged function to this file.
|
||||
|
||||
2009-07-09 Proxmox Support Team <support@proxmox.com>
|
||||
|
||||
* pveum: added dummy binary
|
||||
|
68
Makefile
Normal file
68
Makefile
Normal file
@ -0,0 +1,68 @@
|
||||
RELEASE=2.0
|
||||
|
||||
VERSION=1.0
|
||||
PACKAGE=libpve-access-control
|
||||
PKGREL=1
|
||||
|
||||
DESTDIR=
|
||||
PREFIX=/usr
|
||||
BINDIR=${PREFIX}/bin
|
||||
SBINDIR=${PREFIX}/sbin
|
||||
MANDIR=${PREFIX}/share/man
|
||||
DOCDIR=${PREFIX}/share/doc
|
||||
MAN1DIR=${MANDIR}/man1/
|
||||
export PERLDIR=${PREFIX}/share/perl5
|
||||
|
||||
ARCH:=$(shell dpkg-architecture -qDEB_BUILD_ARCH)
|
||||
DEB=${PACKAGE}_${VERSION}-${PKGREL}_${ARCH}.deb
|
||||
|
||||
all: ${DEB}
|
||||
|
||||
.PHONY: dinstall
|
||||
dinstall: deb
|
||||
dpkg -i ${DEB}
|
||||
|
||||
.PHONY: install
|
||||
install:
|
||||
install -d ${DESTDIR}${BINDIR}
|
||||
install -d ${DESTDIR}${SBINDIR}
|
||||
install -m 0755 pveum ${DESTDIR}${SBINDIR}
|
||||
make -C PVE install
|
||||
perl -I. ./pveum verifyapi
|
||||
install -d ${DESTDIR}/usr/share/man/man1
|
||||
pod2man -n pveum -s 1 -r "proxmox 2.0" -c "Proxmox Documentation" <pveum | gzip -9 > ${DESTDIR}/usr/share/man/man1/pveum.1.gz
|
||||
|
||||
.PHONY: deb ${DEB}
|
||||
deb ${DEB}:
|
||||
rm -rf debian
|
||||
mkdir debian
|
||||
make DESTDIR=${CURDIR}/debian install
|
||||
install -d -m 0755 debian/DEBIAN
|
||||
sed -e s/@@VERSION@@/${VERSION}/ -e s/@@PKGRELEASE@@/${PKGREL}/ -e s/@@ARCH@@/${ARCH}/ <control.in >debian/DEBIAN/control
|
||||
install -D -m 0644 copyright debian/${DOCDIR}/${PACKAGE}/copyright
|
||||
install -m 0644 changelog.Debian debian/${DOCDIR}/${PACKAGE}/
|
||||
gzip -9 debian/${DOCDIR}/${PACKAGE}/changelog.Debian
|
||||
install -m 0644 ChangeLog debian/${DOCDIR}/${PACKAGE}/changelog
|
||||
gzip -9 debian/${DOCDIR}/${PACKAGE}/changelog
|
||||
dpkg-deb --build debian
|
||||
mv debian.deb ${DEB}
|
||||
#rm -rf debian
|
||||
lintian ${DEB}
|
||||
|
||||
.PHONY: upload
|
||||
upload: ${DEB}
|
||||
umount /pve/${RELEASE}; mount /pve/${RELEASE} -o rw
|
||||
mkdir -p /pve/${RELEASE}/extra
|
||||
rm -f /pve/${RELEASE}/extra/${PACKAGE}_*.deb
|
||||
rm -f /pve/${RELEASE}/extra/Packages*
|
||||
cp ${DEB} /pve/${RELEASE}/extra
|
||||
cd /pve/${RELEASE}/extra; dpkg-scanpackages . /dev/null > Packages; gzip -9c Packages > Packages.gz
|
||||
umount /pve/${RELEASE}; mount /pve/${RELEASE} -o ro
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf debian *~ *.deb ${PACKAGE}-*.tar.gz
|
||||
find . -name '*~' -exec rm {} ';'
|
||||
|
||||
.PHONY: distclean
|
||||
distclean: clean
|
170
PVE/API2/ACL.pm
Normal file
170
PVE/API2/ACL.pm
Normal file
@ -0,0 +1,170 @@
|
||||
package PVE::API2::ACL;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use PVE::Cluster qw (cfs_read_file cfs_write_file);
|
||||
use PVE::Tools qw(split_list);
|
||||
use PVE::AccessControl;
|
||||
use PVE::Exception qw(raise_param_exc);
|
||||
|
||||
use PVE::SafeSyslog;
|
||||
|
||||
use Data::Dumper; # fixme: remove
|
||||
|
||||
use PVE::RESTHandler;
|
||||
|
||||
use base qw(PVE::RESTHandler);
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'read_acl',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
description => "Get Access Control List (ACLs).",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
path => { type => 'string' },
|
||||
type => { type => 'string', enum => ['user', 'group'] },
|
||||
ugid => { type => 'string' },
|
||||
roleid => { type => 'string' },
|
||||
propagate => { type => 'boolean' },
|
||||
},
|
||||
},
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
my $res = [];
|
||||
|
||||
my $usercfg = cfs_read_file("user.cfg");
|
||||
|
||||
if (!$usercfg || !$usercfg->{acl}) {
|
||||
return {};
|
||||
}
|
||||
|
||||
my $acl = $usercfg->{acl};
|
||||
foreach my $path (keys %$acl) {
|
||||
foreach my $type (qw(users groups)) {
|
||||
my $d = $acl->{$path}->{$type};
|
||||
next if !$d;
|
||||
foreach my $id (keys %$d) {
|
||||
foreach my $role (keys %{$d->{$id}}) {
|
||||
my $propagate = $d->{$id}->{$role};
|
||||
push @$res, {
|
||||
path => $path,
|
||||
type => $type eq 'groups' ? 'group' : 'user',
|
||||
ugid => $id,
|
||||
roleid => $role,
|
||||
propagate => $propagate,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
}});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'update_acl',
|
||||
protected => 1,
|
||||
path => '',
|
||||
method => 'PUT',
|
||||
description => "Update Access Control List (add or remove permissions).",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
path => {
|
||||
description => "Access control path",
|
||||
type => 'string',
|
||||
},
|
||||
users => {
|
||||
description => "List of users.",
|
||||
type => 'string', format => 'pve-userid-list',
|
||||
optional => 1,
|
||||
},
|
||||
groups => {
|
||||
description => "List of groups.",
|
||||
type => 'string', format => 'pve-groupid-list',
|
||||
optional => 1,
|
||||
},
|
||||
roles => {
|
||||
description => "List of roles.",
|
||||
type => 'string', format => 'pve-roleid-list',
|
||||
},
|
||||
propagate => {
|
||||
description => "Allow to propagate (inherit) permissions.",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
delete => {
|
||||
description => "Remove permissions (instead of adding it).",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
if (!($param->{users} || $param->{groups})) {
|
||||
raise_param_exc({
|
||||
users => "either 'users' or 'groups' is required.",
|
||||
groups => "either 'users' or 'groups' is required." });
|
||||
}
|
||||
|
||||
my $path = PVE::AccessControl::normalize_path($param->{path});
|
||||
raise_param_exc({ path => "invalid ACL path '$param->{path}'" }) if !$path;
|
||||
|
||||
PVE::AccessControl::lock_user_config(
|
||||
sub {
|
||||
|
||||
my $cfg = cfs_read_file("user.cfg");
|
||||
|
||||
my $propagate = $param->{propagate} ? 1 : 0;
|
||||
|
||||
foreach my $role (split_list($param->{roles})) {
|
||||
die "role '$role' does not exist\n"
|
||||
if !$cfg->{roles}->{$role};
|
||||
|
||||
foreach my $group (split_list($param->{groups})) {
|
||||
|
||||
die "group '$group' does not exist\n"
|
||||
if !$cfg->{groups}->{$group};
|
||||
|
||||
if ($param->{delete}) {
|
||||
delete($cfg->{acl}->{$path}->{groups}->{$group}->{$role});
|
||||
} else {
|
||||
$cfg->{acl}->{$path}->{groups}->{$group}->{$role} = $propagate;
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $userid (split_list($param->{users})) {
|
||||
my $username = PVE::AccessControl::verify_username($userid);
|
||||
|
||||
die "user '$username' does not exist\n"
|
||||
if !$cfg->{users}->{$username};
|
||||
|
||||
if ($param->{delete}) {
|
||||
delete($cfg->{acl}->{$path}->{users}->{$username}->{$role});
|
||||
} else {
|
||||
$cfg->{acl}->{$path}->{users}->{$username}->{$role} = $propagate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfs_write_file("user.cfg", $cfg);
|
||||
}, "ACL update failed");
|
||||
|
||||
return undef;
|
||||
}});
|
||||
|
||||
1;
|
175
PVE/API2/AccessControl.pm
Normal file
175
PVE/API2/AccessControl.pm
Normal file
@ -0,0 +1,175 @@
|
||||
package PVE::API2::AccessControl;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use PVE::SafeSyslog;
|
||||
use PVE::RPCEnvironment;
|
||||
use PVE::Cluster;
|
||||
use PVE::RESTHandler;
|
||||
use PVE::AccessControl;
|
||||
use PVE::JSONSchema qw(get_standard_option);
|
||||
use PVE::API2::Domains;
|
||||
use PVE::API2::User;
|
||||
use PVE::API2::Group;
|
||||
use PVE::API2::Role;
|
||||
use PVE::API2::ACL;
|
||||
|
||||
use base qw(PVE::RESTHandler);
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
subclass => "PVE::API2::User",
|
||||
path => 'users',
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
subclass => "PVE::API2::Group",
|
||||
path => 'groups',
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
subclass => "PVE::API2::Role",
|
||||
path => 'roles',
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
subclass => "PVE::API2::ACL",
|
||||
path => 'acl',
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
subclass => "PVE::API2::Domains",
|
||||
path => 'domains',
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
description => "Directory index.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
subdir => { type => 'string' },
|
||||
},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{subdir}" } ],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
my $res = [];
|
||||
|
||||
my $ma = __PACKAGE__->method_attributes();
|
||||
|
||||
foreach my $info (@$ma) {
|
||||
next if !$info->{subclass};
|
||||
|
||||
my $subpath = $info->{match_re}->[0];
|
||||
|
||||
push @$res, { subdir => $subpath };
|
||||
}
|
||||
|
||||
push @$res, { subdir => 'ticket' };
|
||||
|
||||
return $res;
|
||||
}});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'create_ticket',
|
||||
path => 'ticket',
|
||||
method => 'POST',
|
||||
permissions => { user => 'world' },
|
||||
protected => 1, # else we can't access shadow files
|
||||
description => "Create authentication ticket.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
username => {
|
||||
description => "User name",
|
||||
type => 'string',
|
||||
maxLength => 64,
|
||||
},
|
||||
realm => get_standard_option('realm', {
|
||||
description => "You can optionally pass the realm using this parameter. Normally the realm is simply added to the username <username>\@<relam>.",
|
||||
optional => 1}),
|
||||
password => {
|
||||
description => "The secret password. This can also be a valid ticket.",
|
||||
type => 'string',
|
||||
},
|
||||
path => {
|
||||
description => "Only create ticket if user have access 'privs' on 'path'",
|
||||
type => 'string',
|
||||
requires => 'privs',
|
||||
optional => 1,
|
||||
maxLength => 64,
|
||||
},
|
||||
privs => {
|
||||
description => "Only create ticket if user have access 'privs' on 'path'",
|
||||
type => 'string' , format => 'pve-priv-list',
|
||||
requires => 'path',
|
||||
optional => 1,
|
||||
maxLength => 64,
|
||||
},
|
||||
}
|
||||
},
|
||||
returns => {
|
||||
type => "object",
|
||||
properties => {
|
||||
ticket => { type => 'string' },
|
||||
username => { type => 'string' },
|
||||
CSRFPreventionToken => { type => 'string' },
|
||||
}
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
my $username = $param->{username};
|
||||
$username .= "\@$param->{realm}" if $param->{realm};
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $clientip = $rpcenv->get_client_ip() || '';
|
||||
|
||||
my $ticket;
|
||||
my $token;
|
||||
eval {
|
||||
|
||||
if ($param->{path} && $param->{privs}) {
|
||||
my $privs = [ PVE::Tools::split_list($param->{privs}) ];
|
||||
my $path = PVE::AccessControl::normalize_path($param->{path});
|
||||
if (!($path && scalar(@$privs) && $rpcenv->check($username, $path, $privs))) {
|
||||
die "no permission ($param->{path}, $param->{privs})\n";
|
||||
}
|
||||
}
|
||||
|
||||
my $tmp;
|
||||
if (($tmp = PVE::AccessControl::verify_ticket($param->{password}, 1)) &&
|
||||
($tmp eq $username)) {
|
||||
# got valid ticket
|
||||
} else {
|
||||
$username = PVE::AccessControl::authenticate_user($username, $param->{password});
|
||||
}
|
||||
$ticket = PVE::AccessControl::assemble_ticket($username);
|
||||
$token = PVE::AccessControl::assemble_csrf_prevention_token($username);
|
||||
};
|
||||
if (my $err = $@) {
|
||||
syslog('err', "authentication failure; rhost=$clientip user=$username msg=$err");
|
||||
die $err;
|
||||
}
|
||||
|
||||
PVE::Cluster::log_msg('info', 'root@pam', "successful auth for user '$username'");
|
||||
|
||||
return {
|
||||
ticket => $ticket,
|
||||
username => $username,
|
||||
CSRFPreventionToken => $token,
|
||||
};
|
||||
}});
|
||||
|
||||
1;
|
304
PVE/API2/Domains.pm
Normal file
304
PVE/API2/Domains.pm
Normal file
@ -0,0 +1,304 @@
|
||||
package PVE::API2::Domains;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use PVE::Cluster qw (cfs_read_file cfs_write_file);
|
||||
use PVE::AccessControl;
|
||||
use PVE::JSONSchema qw(get_standard_option);
|
||||
|
||||
use PVE::SafeSyslog;
|
||||
|
||||
use Data::Dumper; # fixme: remove
|
||||
|
||||
use PVE::RESTHandler;
|
||||
|
||||
my $domainconfigfile = "domains.cfg";
|
||||
|
||||
use base qw(PVE::RESTHandler);
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
description => "Authentication domain index.",
|
||||
permissions => { user => 'world' },
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
realm => { type => 'string' },
|
||||
comment => { type => 'string', optional => 1 },
|
||||
},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{realm}" } ],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
my $res = [];
|
||||
|
||||
my $cfg = cfs_read_file($domainconfigfile);
|
||||
|
||||
foreach my $realm (keys %$cfg) {
|
||||
my $d = $cfg->{$realm};
|
||||
my $entry = { realm => $realm, type => $d->{type} };
|
||||
$entry->{comment} = $d->{comment} if $d->{comment};
|
||||
$entry->{default} = 1 if $d->{default};
|
||||
push @$res, $entry;
|
||||
}
|
||||
|
||||
return $res;
|
||||
}});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'create',
|
||||
protected => 1,
|
||||
path => '',
|
||||
method => 'POST',
|
||||
description => "Add an authentication server.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
realm => get_standard_option('realm'),
|
||||
type => {
|
||||
description => "Server type.",
|
||||
type => 'string',
|
||||
enum => [ 'ad', 'ldap' ],
|
||||
},
|
||||
server1 => {
|
||||
description => "Server IP address (or DNS name)",
|
||||
type => 'string',
|
||||
},
|
||||
server2 => {
|
||||
description => "Fallback Server IP address (or DNS name)",
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
secure => {
|
||||
description => "Use secure LDAPS protocol.",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
default => {
|
||||
description => "Use this as default realm",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
comment => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
port => {
|
||||
description => "Server port",
|
||||
type => 'integer',
|
||||
minimum => 1,
|
||||
maximum => 65535,
|
||||
optional => 1,
|
||||
},
|
||||
base_dn => {
|
||||
description => "LDAP base domain name",
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
user_attr => {
|
||||
description => "LDAP user attribute name",
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::AccessControl::lock_domain_config(
|
||||
sub {
|
||||
|
||||
my $cfg = cfs_read_file($domainconfigfile);
|
||||
|
||||
my $realm = $param->{realm};
|
||||
|
||||
die "domain '$realm' already exists\n"
|
||||
if $cfg->{$realm};
|
||||
|
||||
die "unable to use reserved name '$realm'\n"
|
||||
if ($realm eq 'pam' || $realm eq 'pve');
|
||||
|
||||
if (defined($param->{secure})) {
|
||||
$cfg->{$realm}->{secure} = $param->{secure} ? 1 : 0;
|
||||
}
|
||||
|
||||
if ($param->{default}) {
|
||||
foreach my $r (keys %$cfg) {
|
||||
delete $cfg->{$r}->{default};
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $p (keys %$param) {
|
||||
next if $p eq 'realm';
|
||||
$cfg->{$realm}->{$p} = $param->{$p};
|
||||
}
|
||||
|
||||
cfs_write_file($domainconfigfile, $cfg);
|
||||
}, "add auth server failed");
|
||||
|
||||
return undef;
|
||||
}});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'update',
|
||||
path => '{realm}',
|
||||
method => 'PUT',
|
||||
description => "Update authentication server settings.",
|
||||
protected => 1,
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
realm => get_standard_option('realm'),
|
||||
server1 => {
|
||||
description => "Server IP address (or DNS name)",
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
server2 => {
|
||||
description => "Fallback Server IP address (or DNS name)",
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
secure => {
|
||||
description => "Use secure LDAPS protocol.",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
default => {
|
||||
description => "Use this as default realm",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
comment => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
port => {
|
||||
description => "Server port",
|
||||
type => 'integer',
|
||||
minimum => 1,
|
||||
maximum => 65535,
|
||||
optional => 1,
|
||||
},
|
||||
base_dn => {
|
||||
description => "LDAP base domain name",
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
user_attr => {
|
||||
description => "LDAP user attribute name",
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::AccessControl::lock_domain_config(
|
||||
sub {
|
||||
|
||||
my $cfg = cfs_read_file($domainconfigfile);
|
||||
|
||||
my $realm = $param->{realm};
|
||||
delete $param->{realm};
|
||||
|
||||
die "unable to modify bultin domain '$realm'\n"
|
||||
if ($realm eq 'pam' || $realm eq 'pve');
|
||||
|
||||
die "domain '$realm' does not exist\n"
|
||||
if !$cfg->{$realm};
|
||||
|
||||
if (defined($param->{secure})) {
|
||||
$cfg->{$realm}->{secure} = $param->{secure} ? 1 : 0;
|
||||
}
|
||||
|
||||
if ($param->{default}) {
|
||||
foreach my $r (keys %$cfg) {
|
||||
delete $cfg->{$r}->{default};
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $p (keys %$param) {
|
||||
$cfg->{$realm}->{$p} = $param->{$p};
|
||||
}
|
||||
|
||||
cfs_write_file($domainconfigfile, $cfg);
|
||||
}, "update auth server failed");
|
||||
|
||||
return undef;
|
||||
}});
|
||||
|
||||
# fixme: return format!
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'read',
|
||||
path => '{realm}',
|
||||
method => 'GET',
|
||||
description => "Get auth server configuration.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
realm => get_standard_option('realm'),
|
||||
},
|
||||
},
|
||||
returns => {},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
my $cfg = cfs_read_file($domainconfigfile);
|
||||
|
||||
my $realm = $param->{realm};
|
||||
|
||||
my $data = $cfg->{$realm};
|
||||
die "domain '$realm' does not exist\n" if !$data;
|
||||
|
||||
return $data;
|
||||
}});
|
||||
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'delete',
|
||||
path => '{realm}',
|
||||
method => 'DELETE',
|
||||
description => "Delete an authentication server.",
|
||||
protected => 1,
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
realm => get_standard_option('realm'),
|
||||
}
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::AccessControl::lock_user_config(
|
||||
sub {
|
||||
|
||||
my $cfg = cfs_read_file($domainconfigfile);
|
||||
|
||||
my $realm = $param->{realm};
|
||||
|
||||
die "domain '$realm' does not exist\n" if !$cfg->{$realm};
|
||||
|
||||
delete $cfg->{$realm};
|
||||
|
||||
cfs_write_file($domainconfigfile, $cfg);
|
||||
}, "delete auth server failed");
|
||||
|
||||
return undef;
|
||||
}});
|
||||
|
||||
1;
|
206
PVE/API2/Group.pm
Normal file
206
PVE/API2/Group.pm
Normal file
@ -0,0 +1,206 @@
|
||||
package PVE::API2::Group;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use PVE::Cluster qw (cfs_read_file cfs_write_file);
|
||||
use PVE::AccessControl;
|
||||
|
||||
use PVE::SafeSyslog;
|
||||
|
||||
use Data::Dumper; # fixme: remove
|
||||
|
||||
use PVE::RESTHandler;
|
||||
|
||||
use base qw(PVE::RESTHandler);
|
||||
|
||||
my $extract_group_data = sub {
|
||||
my ($data, $full) = @_;
|
||||
|
||||
my $res = {};
|
||||
|
||||
$res->{comment} = $data->{comment} if defined($data->{comment});
|
||||
|
||||
return $res if !$full;
|
||||
|
||||
$res->{users} = $data->{users} ? [ keys %{$data->{users}} ] : [];
|
||||
|
||||
return $res;
|
||||
};
|
||||
|
||||
# fixme: index should return more/all attributes?
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
description => "Group index.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
groupid => { type => 'string' },
|
||||
},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{groupid}" } ],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
my $res = [];
|
||||
|
||||
my $usercfg = cfs_read_file("user.cfg");
|
||||
|
||||
foreach my $group (keys %{$usercfg->{groups}}) {
|
||||
my $entry = &$extract_group_data($usercfg->{groups}->{$group});
|
||||
$entry->{groupid} = $group;
|
||||
push @$res, $entry;
|
||||
}
|
||||
|
||||
return $res;
|
||||
}});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'create_group',
|
||||
protected => 1,
|
||||
path => '',
|
||||
method => 'POST',
|
||||
description => "Create new group.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
groupid => { type => 'string', format => 'pve-groupid' },
|
||||
comment => { type => 'string', optional => 1 },
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::AccessControl::lock_user_config(
|
||||
sub {
|
||||
|
||||
my $usercfg = cfs_read_file("user.cfg");
|
||||
|
||||
my $group = $param->{groupid};
|
||||
|
||||
die "group '$group' already exists\n"
|
||||
if $usercfg->{groups}->{$group};
|
||||
|
||||
$usercfg->{groups}->{$group} = { users => {} };
|
||||
|
||||
$usercfg->{groups}->{$group}->{comment} = $param->{comment} if $param->{comment};
|
||||
|
||||
|
||||
cfs_write_file("user.cfg", $usercfg);
|
||||
}, "create group failed");
|
||||
|
||||
return undef;
|
||||
}});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'update_group',
|
||||
protected => 1,
|
||||
path => '{groupid}',
|
||||
method => 'PUT',
|
||||
description => "Update group data.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
# fixme: set/delete members
|
||||
groupid => { type => 'string', format => 'pve-groupid' },
|
||||
comment => { type => 'string', optional => 1 },
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::AccessControl::lock_user_config(
|
||||
sub {
|
||||
|
||||
my $usercfg = cfs_read_file("user.cfg");
|
||||
|
||||
my $group = $param->{groupid};
|
||||
|
||||
my $data = $usercfg->{groups}->{$group};
|
||||
|
||||
die "group '$group' does not exist\n"
|
||||
if !$data;
|
||||
|
||||
$data->{comment} = $param->{comment} if $param->{comment};
|
||||
|
||||
cfs_write_file("user.cfg", $usercfg);
|
||||
}, "create group failed");
|
||||
|
||||
return undef;
|
||||
}});
|
||||
|
||||
# fixme: return format!
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'read_group',
|
||||
path => '{groupid}',
|
||||
method => 'GET',
|
||||
description => "Get group configuration.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
groupid => { type => 'string', format => 'pve-groupid' },
|
||||
},
|
||||
},
|
||||
returns => {},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
my $group = $param->{groupid};
|
||||
|
||||
my $usercfg = cfs_read_file("user.cfg");
|
||||
|
||||
my $data = $usercfg->{groups}->{$group};
|
||||
|
||||
die "group '$group' does not exist\n" if !$data;
|
||||
|
||||
return &$extract_group_data($data, 1);
|
||||
}});
|
||||
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'delete_group',
|
||||
protected => 1,
|
||||
path => '{groupid}',
|
||||
method => 'DELETE',
|
||||
description => "Delete group.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
groupid => { type => 'string' , format => 'pve-groupid' },
|
||||
}
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::AccessControl::lock_user_config(
|
||||
sub {
|
||||
|
||||
my $usercfg = cfs_read_file("user.cfg");
|
||||
|
||||
my $group = $param->{groupid};
|
||||
|
||||
die "group '$group' does not exist\n"
|
||||
if !$usercfg->{groups}->{$group};
|
||||
|
||||
delete ($usercfg->{groups}->{$group});
|
||||
|
||||
PVE::AccessControl::delete_group_acl($group, $usercfg);
|
||||
|
||||
cfs_write_file("user.cfg", $usercfg);
|
||||
}, "delete group failed");
|
||||
|
||||
return undef;
|
||||
}});
|
||||
|
||||
1;
|
12
PVE/API2/Makefile
Normal file
12
PVE/API2/Makefile
Normal file
@ -0,0 +1,12 @@
|
||||
|
||||
API2_SOURCES= \
|
||||
AccessControl.pm \
|
||||
Domains.pm \
|
||||
ACL.pm \
|
||||
Role.pm \
|
||||
Group.pm \
|
||||
User.pm
|
||||
|
||||
.PHONY: install
|
||||
install:
|
||||
for i in ${API2_SOURCES}; do install -D -m 0644 $$i ${DESTDIR}${PERLDIR}/PVE/API2/$$i; done
|
193
PVE/API2/Role.pm
Normal file
193
PVE/API2/Role.pm
Normal file
@ -0,0 +1,193 @@
|
||||
package PVE::API2::Role;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use PVE::Cluster qw (cfs_read_file cfs_write_file);
|
||||
use PVE::AccessControl;
|
||||
|
||||
use PVE::SafeSyslog;
|
||||
|
||||
use Data::Dumper; # fixme: remove
|
||||
|
||||
use PVE::RESTHandler;
|
||||
|
||||
use base qw(PVE::RESTHandler);
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
description => "Role index.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
roleid => { type => 'string' },
|
||||
},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{roleid}" } ],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
my $res = [];
|
||||
|
||||
my $usercfg = cfs_read_file("user.cfg");
|
||||
|
||||
foreach my $role (keys %{$usercfg->{roles}}) {
|
||||
my $privs = join(',', sort keys %{$usercfg->{roles}->{$role}});
|
||||
push @$res, { roleid => $role, privs => $privs };
|
||||
}
|
||||
|
||||
return $res;
|
||||
}});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'create_role',
|
||||
protected => 1,
|
||||
path => '',
|
||||
method => 'POST',
|
||||
description => "Create new role.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
roleid => { type => 'string', format => 'pve-roleid' },
|
||||
privs => { type => 'string' , format => 'pve-priv-list', optional => 1 },
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::AccessControl::lock_user_config(
|
||||
sub {
|
||||
|
||||
my $usercfg = cfs_read_file("user.cfg");
|
||||
|
||||
my $role = $param->{roleid};
|
||||
|
||||
die "role '$role' already exists\n"
|
||||
if $usercfg->{roles}->{$role};
|
||||
|
||||
$usercfg->{roles}->{$role} = {};
|
||||
|
||||
PVE::AccessControl::add_role_privs($role, $usercfg, $param->{privs});
|
||||
|
||||
cfs_write_file("user.cfg", $usercfg);
|
||||
}, "create role failed");
|
||||
|
||||
return undef;
|
||||
}});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'update_role',
|
||||
protected => 1,
|
||||
path => '{roleid}',
|
||||
method => 'PUT',
|
||||
description => "Create new role.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
roleid => { type => 'string', format => 'pve-roleid' },
|
||||
privs => { type => 'string' , format => 'pve-priv-list' },
|
||||
append => {
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
requires => 'privs',
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::AccessControl::lock_user_config(
|
||||
sub {
|
||||
|
||||
my $role = $param->{roleid};
|
||||
|
||||
my $usercfg = cfs_read_file("user.cfg");
|
||||
|
||||
die "role '$role' does not exist\n"
|
||||
if !$usercfg->{roles}->{$role};
|
||||
|
||||
$usercfg->{roles}->{$role} = {} if !$param->{append};
|
||||
|
||||
PVE::AccessControl::add_role_privs($role, $usercfg, $param->{privs});
|
||||
|
||||
cfs_write_file("user.cfg", $usercfg);
|
||||
}, "update role failed");
|
||||
|
||||
return undef;
|
||||
}});
|
||||
|
||||
# fixme: return format!
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'read_role',
|
||||
path => '{roleid}',
|
||||
method => 'GET',
|
||||
description => "Get role configuration.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
roleid => { type => 'string' , format => 'pve-roleid' },
|
||||
},
|
||||
},
|
||||
returns => {},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
my $usercfg = cfs_read_file("user.cfg");
|
||||
|
||||
my $role = $param->{roleid};
|
||||
|
||||
my $data = $usercfg->{roles}->{$role};
|
||||
|
||||
die "role '$role' does not exist\n" if !$data;
|
||||
|
||||
return $data;
|
||||
}});
|
||||
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'delete_role',
|
||||
protected => 1,
|
||||
path => '{roleid}',
|
||||
method => 'DELETE',
|
||||
description => "Delete role.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
roleid => { type => 'string', format => 'pve-roleid' },
|
||||
}
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::AccessControl::lock_user_config(
|
||||
sub {
|
||||
|
||||
my $role = $param->{roleid};
|
||||
|
||||
my $usercfg = cfs_read_file("user.cfg");
|
||||
|
||||
die "role '$role' does not exist\n"
|
||||
if !$usercfg->{roles}->{$role};
|
||||
|
||||
delete ($usercfg->{roles}->{$role});
|
||||
|
||||
# fixme: delete role from acl?
|
||||
|
||||
cfs_write_file("user.cfg", $usercfg);
|
||||
}, "delete role failed");
|
||||
|
||||
return undef;
|
||||
}});
|
||||
|
||||
1;
|
300
PVE/API2/User.pm
Normal file
300
PVE/API2/User.pm
Normal file
@ -0,0 +1,300 @@
|
||||
package PVE::API2::User;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use PVE::Cluster qw (cfs_read_file cfs_write_file);
|
||||
use PVE::Tools qw(split_list);
|
||||
use PVE::AccessControl;
|
||||
use PVE::JSONSchema qw(get_standard_option);
|
||||
|
||||
use PVE::SafeSyslog;
|
||||
|
||||
use Data::Dumper; # fixme: remove
|
||||
|
||||
use PVE::RESTHandler;
|
||||
|
||||
use base qw(PVE::RESTHandler);
|
||||
|
||||
my $extract_user_data = sub {
|
||||
my ($data, $full) = @_;
|
||||
|
||||
my $res = {};
|
||||
|
||||
foreach my $prop (qw(enable expire firstname lastname email comment)) {
|
||||
$res->{$prop} = $data->{$prop} if defined($data->{$prop});
|
||||
}
|
||||
|
||||
return $res if !$full;
|
||||
|
||||
$res->{groups} = $data->{groups} ? [ keys %{$data->{groups}} ] : [];
|
||||
|
||||
return $res;
|
||||
};
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
description => "User index.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
userid => { type => 'string' },
|
||||
},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{userid}" } ],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
my $res = [];
|
||||
|
||||
my $usercfg = cfs_read_file("user.cfg");
|
||||
|
||||
foreach my $user (keys %{$usercfg->{users}}) {
|
||||
next if $user eq 'root';
|
||||
|
||||
my $entry = &$extract_user_data($usercfg->{users}->{$user});
|
||||
$entry->{userid} = $user;
|
||||
push @$res, $entry;
|
||||
}
|
||||
|
||||
return $res;
|
||||
}});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'create_user',
|
||||
protected => 1,
|
||||
path => '',
|
||||
method => 'POST',
|
||||
description => "Create new user.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
userid => get_standard_option('userid'),
|
||||
password => { type => 'string', optional => 1 },
|
||||
groups => { type => 'string', optional => 1, format => 'pve-groupid-list'},
|
||||
firstname => { type => 'string', optional => 1 },
|
||||
lastname => { type => 'string', optional => 1 },
|
||||
email => { type => 'string', optional => 1, format => 'email-opt' },
|
||||
comment => { type => 'string', optional => 1 },
|
||||
expire => {
|
||||
description => "Account expiration date (seconds since epoch). '0' means no expiration date.",
|
||||
type => 'integer',
|
||||
minimum => 0,
|
||||
optional => 1,
|
||||
},
|
||||
enable => {
|
||||
description => "Enable the account (default). You can set this to '0' to disable the accout",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
default => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::AccessControl::lock_user_config(
|
||||
sub {
|
||||
|
||||
my ($username, $ruid, $realm) = PVE::AccessControl::verify_username($param->{userid});
|
||||
|
||||
my $usercfg = cfs_read_file("user.cfg");
|
||||
|
||||
die "user '$username' already exists\n"
|
||||
if $usercfg->{users}->{$username};
|
||||
|
||||
PVE::AccessControl::domain_set_password($realm, $ruid, $param->{password})
|
||||
if $param->{password};
|
||||
|
||||
my $enable = defined($param->{enable}) ? $param->{enable} : 1;
|
||||
$usercfg->{users}->{$username} = { enable => $enable };
|
||||
$usercfg->{users}->{$username}->{expire} = $param->{expire} if $param->{expire};
|
||||
|
||||
if ($param->{groups}) {
|
||||
foreach my $group (split_list($param->{groups})) {
|
||||
if ($usercfg->{groups}->{$group}) {
|
||||
PVE::AccessControl::add_user_group($username, $usercfg, $group);
|
||||
} else {
|
||||
die "no such group '$group'\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$usercfg->{users}->{$username}->{firstname} = $param->{firstname} if $param->{firstname};
|
||||
$usercfg->{users}->{$username}->{lastname} = $param->{lastname} if $param->{lastname};
|
||||
$usercfg->{users}->{$username}->{email} = $param->{email} if $param->{email};
|
||||
$usercfg->{users}->{$username}->{comment} = $param->{comment} if $param->{comment};
|
||||
|
||||
cfs_write_file("user.cfg", $usercfg);
|
||||
}, "create user failed");
|
||||
|
||||
return undef;
|
||||
}});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'read_user',
|
||||
path => '{userid}',
|
||||
method => 'GET',
|
||||
description => "Get user configuration.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
userid => get_standard_option('userid'),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
enable => { type => 'boolean' },
|
||||
expire => { type => 'integer', optional => 1 },
|
||||
firstname => { type => 'string', optional => 1 },
|
||||
lastname => { type => 'string', optional => 1 },
|
||||
email => { type => 'string', optional => 1 },
|
||||
comment => { type => 'string', optional => 1 },
|
||||
groups => { type => 'array' },
|
||||
}
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
my ($username, undef, $domain) =
|
||||
PVE::AccessControl::verify_username($param->{userid});
|
||||
|
||||
my $usercfg = cfs_read_file("user.cfg");
|
||||
|
||||
my $data = $usercfg->{users}->{$username};
|
||||
|
||||
die "user '$username' does not exist\n" if !$data;
|
||||
|
||||
return &$extract_user_data($data, 1);
|
||||
}});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'update_user',
|
||||
protected => 1,
|
||||
path => '{userid}',
|
||||
method => 'PUT',
|
||||
description => "Update user configuration.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
userid => get_standard_option('userid'),
|
||||
password => { type => 'string', optional => 1 },
|
||||
groups => { type => 'string', optional => 1, format => 'pve-groupid-list' },
|
||||
append => {
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
requires => 'groups',
|
||||
},
|
||||
enable => {
|
||||
description => "Enable/disable the account.",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
firstname => { type => 'string', optional => 1 },
|
||||
lastname => { type => 'string', optional => 1 },
|
||||
email => { type => 'string', optional => 1, format => 'email-opt' },
|
||||
comment => { type => 'string', optional => 1 },
|
||||
expire => {
|
||||
description => "Account expiration date (seconds since epoch). '0' means no expiration date.",
|
||||
type => 'integer',
|
||||
minimum => 0,
|
||||
optional => 1
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::AccessControl::lock_user_config(
|
||||
sub {
|
||||
|
||||
my ($username, $ruid, $realm) =
|
||||
PVE::AccessControl::verify_username($param->{userid});
|
||||
|
||||
my $usercfg = cfs_read_file("user.cfg");
|
||||
|
||||
die "user '$username' does not exist\n"
|
||||
if !$usercfg->{users}->{$username};
|
||||
|
||||
PVE::AccessControl::domain_set_password($realm, $ruid, $param->{password})
|
||||
if $param->{password};
|
||||
|
||||
$usercfg->{users}->{$username}->{enable} = $param->{enable} if defined($param->{enable});
|
||||
|
||||
$usercfg->{users}->{$username}->{expire} = $param->{expire} if defined($param->{expire});
|
||||
|
||||
PVE::AccessControl::delete_user_group($username, $usercfg)
|
||||
if (!$param->{append} && $param->{groups});
|
||||
|
||||
if ($param->{groups}) {
|
||||
foreach my $group (split_list($param->{groups})) {
|
||||
if ($usercfg->{groups}->{$group}) {
|
||||
PVE::AccessControl::add_user_group($username, $usercfg, $group);
|
||||
} else {
|
||||
die "no such group '$group'\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$usercfg->{users}->{$username}->{firstname} = $param->{firstname} if defined($param->{firstname});
|
||||
$usercfg->{users}->{$username}->{lastname} = $param->{lastname} if defined($param->{lastname});
|
||||
$usercfg->{users}->{$username}->{email} = $param->{email} if defined($param->{email});
|
||||
$usercfg->{users}->{$username}->{comment} = $param->{comment} if defined($param->{comment});
|
||||
|
||||
cfs_write_file("user.cfg", $usercfg);
|
||||
}, "update user failed");
|
||||
|
||||
return undef;
|
||||
}});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'delete_user',
|
||||
protected => 1,
|
||||
path => '{userid}',
|
||||
method => 'DELETE',
|
||||
description => "Delete user.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
userid => get_standard_option('userid'),
|
||||
}
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::AccessControl::lock_user_config(
|
||||
sub {
|
||||
|
||||
my ($username, $ruid, $realm) =
|
||||
PVE::AccessControl::verify_username($param->{userid});
|
||||
|
||||
my $usercfg = cfs_read_file("user.cfg");
|
||||
|
||||
die "user '$username' does not exist\n"
|
||||
if !$usercfg->{users}->{$username};
|
||||
|
||||
delete ($usercfg->{users}->{$username});
|
||||
|
||||
PVE::AccessControl::delete_shadow_password($ruid) if $realm eq 'pve';
|
||||
PVE::AccessControl::delete_user_group($username, $usercfg);
|
||||
PVE::AccessControl::delete_user_acl($username, $usercfg);
|
||||
|
||||
cfs_write_file("user.cfg", $usercfg);
|
||||
}, "delete user failed");
|
||||
|
||||
return undef;
|
||||
}});
|
||||
|
||||
1;
|
1235
PVE/AccessControl.pm
Normal file
1235
PVE/AccessControl.pm
Normal file
File diff suppressed because it is too large
Load Diff
7
PVE/Makefile
Normal file
7
PVE/Makefile
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
|
||||
.PHONY: install
|
||||
install:
|
||||
install -D -m 0644 AccessControl.pm ${DESTDIR}${PERLDIR}/PVE/AccessControl.pm
|
||||
install -D -m 0644 RPCEnvironment.pm ${DESTDIR}${PERLDIR}/PVE/RPCEnvironment.pm
|
||||
make -C API2 install
|
672
PVE/RPCEnvironment.pm
Normal file
672
PVE/RPCEnvironment.pm
Normal file
@ -0,0 +1,672 @@
|
||||
package PVE::RPCEnvironment;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use POSIX ":sys_wait_h";
|
||||
use IO::File;
|
||||
use Fcntl qw(:flock);
|
||||
use PVE::SafeSyslog;
|
||||
use PVE::Tools;
|
||||
use PVE::INotify;
|
||||
use PVE::Cluster;
|
||||
use PVE::ProcFSTools;
|
||||
use PVE::AccessControl;
|
||||
|
||||
# we use this singleton class to pass RPC related environment values
|
||||
|
||||
my $pve_env;
|
||||
|
||||
# save $SIG{CHLD} handler implementation.
|
||||
# simply set $SIG{CHLD} = $worker_reaper;
|
||||
# and register forked processes with &$register_worker(pid)
|
||||
# Note: using $SIG{CHLD} = 'IGNORE' or $SIG{CHLD} = sub { wait (); } or ...
|
||||
# has serious side effects, because perls built in system() and open()
|
||||
# functions can't get the correct exit status of a child. So we cant use
|
||||
# that (also see perlipc)
|
||||
|
||||
my $WORKER_PIDS;
|
||||
|
||||
my $log_task_result = sub {
|
||||
my ($upid, $user, $status) = @_;
|
||||
|
||||
my $msg = 'successful';
|
||||
my $pri = 'info';
|
||||
if ($status != 0) {
|
||||
my $ec = $status >> 8;
|
||||
my $ic = $status & 255;
|
||||
$msg = $ec ? "failed ($ec)" : "interrupted ($ic)";
|
||||
$pri = 'err';
|
||||
}
|
||||
my $tlist = active_workers($upid);
|
||||
PVE::Cluster::broadcast_tasklist($tlist);
|
||||
my $task;
|
||||
foreach my $t (@$tlist) {
|
||||
if ($t->{upid} eq $upid) {
|
||||
$task = $t;
|
||||
last;
|
||||
}
|
||||
}
|
||||
if ($task && $task->{status}) {
|
||||
$msg = $task->{status};
|
||||
}
|
||||
PVE::Cluster::log_msg($pri, $user, "end task $upid $msg");
|
||||
};
|
||||
|
||||
my $worker_reaper = sub {
|
||||
local $!; local $?;
|
||||
foreach my $pid (keys %$WORKER_PIDS) {
|
||||
my $waitpid = waitpid ($pid, WNOHANG);
|
||||
if (defined($waitpid) && ($waitpid == $pid)) {
|
||||
my $info = $WORKER_PIDS->{$pid};
|
||||
if ($info && $info->{upid} && $info->{user}) {
|
||||
&$log_task_result($info->{upid}, $info->{user}, $?);
|
||||
}
|
||||
delete ($WORKER_PIDS->{$pid});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
my $register_worker = sub {
|
||||
my ($pid, $user, $upid) = @_;
|
||||
|
||||
return if !$pid;
|
||||
|
||||
# do not register if already finished
|
||||
my $waitpid = waitpid ($pid, WNOHANG);
|
||||
if (defined($waitpid) && ($waitpid == $pid)) {
|
||||
delete ($WORKER_PIDS->{$pid});
|
||||
return;
|
||||
}
|
||||
|
||||
$WORKER_PIDS->{$pid} = {
|
||||
user => $user,
|
||||
upid => $upid,
|
||||
};
|
||||
};
|
||||
|
||||
# ACL cache
|
||||
|
||||
my $compile_acl = sub {
|
||||
my ($self, $user) = @_;
|
||||
|
||||
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'}};
|
||||
}
|
||||
|
||||
foreach my $path (sort keys %{$cfg->{acl}}) {
|
||||
my @ra = PVE::AccessControl::roles($cfg, $user, $path);
|
||||
|
||||
my $privs = {};
|
||||
foreach my $role (@ra) {
|
||||
if (my $privset = $cfg->{roles}->{$role}) {
|
||||
foreach my $p (keys %$privset) {
|
||||
$privs->{$p} = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$res->{$path} = $privs;
|
||||
}
|
||||
|
||||
return $res;
|
||||
};
|
||||
|
||||
sub permissions {
|
||||
my ($self, $user, $path) = @_;
|
||||
|
||||
$user = PVE::AccessControl::verify_username($user, 1);
|
||||
return {} if !$user;
|
||||
|
||||
my $cache = $self->{aclcache};
|
||||
|
||||
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/|));
|
||||
|
||||
$perm = $acl->{$p};
|
||||
}
|
||||
$acl->{$path} = $perm;
|
||||
}
|
||||
|
||||
return $perm;
|
||||
}
|
||||
|
||||
sub check {
|
||||
my ($self, $user, $path, $privs) = @_;
|
||||
|
||||
my $perm = $self->permissions($user, $path);
|
||||
|
||||
foreach my $priv (@$privs) {
|
||||
return undef if !$perm->{$priv};
|
||||
};
|
||||
|
||||
return 1;
|
||||
};
|
||||
|
||||
sub user_enabled {
|
||||
my ($self, $user) = @_;
|
||||
|
||||
my $cfg = $self->{user_cfg};
|
||||
return PVE::AccessControl::user_enabled($cfg, $user);
|
||||
}
|
||||
|
||||
# initialize environment - must be called once at program startup
|
||||
sub init {
|
||||
my ($class, $type, %params) = @_;
|
||||
|
||||
$class = ref($class) || $class;
|
||||
|
||||
die "already initialized" if $pve_env;
|
||||
|
||||
die "unknown environment type" if !$type || $type !~ m/^(cli|pub|priv)$/;
|
||||
|
||||
$SIG{CHLD} = $worker_reaper;
|
||||
|
||||
# environment types
|
||||
# cli ... command started fron command line
|
||||
# pub ... access from public server (apache)
|
||||
# priv ... access from private server (pvedaemon)
|
||||
|
||||
my $self = {
|
||||
user_cfg => {},
|
||||
aclcache => {},
|
||||
aclversion => undef,
|
||||
type => $type,
|
||||
};
|
||||
|
||||
bless $self, $class;
|
||||
|
||||
foreach my $p (keys %params) {
|
||||
if ($p eq 'atfork') {
|
||||
$self->{$p} = $params{$p};
|
||||
} else {
|
||||
die "unknown option '$p'";
|
||||
}
|
||||
}
|
||||
|
||||
$pve_env = $self;
|
||||
|
||||
my ($sysname, $nodename) = POSIX::uname();
|
||||
|
||||
$nodename =~ s/\..*$//; # strip domain part, if any
|
||||
|
||||
$self->{nodename} = $nodename;
|
||||
|
||||
return $self;
|
||||
};
|
||||
|
||||
# get the singleton
|
||||
sub get {
|
||||
|
||||
die "not initialized" if !$pve_env;
|
||||
|
||||
return $pve_env;
|
||||
}
|
||||
|
||||
# init_request - must be called before each RPC request
|
||||
sub init_request {
|
||||
my ($self, %params) = @_;
|
||||
|
||||
PVE::Cluster::cfs_update();
|
||||
|
||||
my $userconfig; # we use this for regression tests
|
||||
foreach my $p (keys %params) {
|
||||
if ($p eq 'userconfig') {
|
||||
$userconfig = $params{$p};
|
||||
} else {
|
||||
die "unknown parameter '$p'";
|
||||
}
|
||||
}
|
||||
|
||||
eval {
|
||||
$self->{aclcache} = {};
|
||||
if ($userconfig) {
|
||||
my $ucdata = PVE::Tools::file_get_contents($userconfig);
|
||||
my $cfg = PVE::AccessControl::parse_user_config($userconfig, $ucdata);
|
||||
$self->{user_cfg} = $cfg;
|
||||
} else {
|
||||
my $ucvers = PVE::Cluster::cfs_file_version('user.cfg');
|
||||
if (!$self->{aclcache} || !defined($self->{aclversion}) ||
|
||||
!defined($ucvers) || ($ucvers ne $self->{aclversion})) {
|
||||
$self->{aclversion} = $ucvers;
|
||||
my $cfg = PVE::Cluster::cfs_read_file('user.cfg');
|
||||
$self->{user_cfg} = $cfg;
|
||||
}
|
||||
}
|
||||
};
|
||||
if (my $err = $@) {
|
||||
$self->{user_cfg} = {};
|
||||
die "Unable to load access control list: $err";
|
||||
}
|
||||
}
|
||||
|
||||
sub set_client_ip {
|
||||
my ($self, $ip) = @_;
|
||||
|
||||
$self->{client_ip} = $ip;
|
||||
}
|
||||
|
||||
sub get_client_ip {
|
||||
my ($self) = @_;
|
||||
|
||||
return $self->{client_ip};
|
||||
}
|
||||
|
||||
sub set_result_count {
|
||||
my ($self, $count) = @_;
|
||||
|
||||
$self->{result_count} = $count;
|
||||
}
|
||||
|
||||
sub get_result_count {
|
||||
my ($self) = @_;
|
||||
|
||||
return $self->{result_count};
|
||||
}
|
||||
|
||||
sub set_language {
|
||||
my ($self, $lang) = @_;
|
||||
|
||||
# fixme: initialize I18N
|
||||
|
||||
$self->{language} = $lang;
|
||||
}
|
||||
|
||||
sub get_language {
|
||||
my ($self) = @_;
|
||||
|
||||
return $self->{language};
|
||||
}
|
||||
|
||||
sub set_user {
|
||||
my ($self, $user) = @_;
|
||||
|
||||
# fixme: get ACLs
|
||||
|
||||
$self->{user} = $user;
|
||||
}
|
||||
|
||||
sub get_user {
|
||||
my ($self) = @_;
|
||||
|
||||
die "user name not set\n" if !$self->{user};
|
||||
|
||||
return $self->{user};
|
||||
}
|
||||
|
||||
# read/update list of active workers
|
||||
# we move all finished tasks to the archive index,
|
||||
# but keep aktive and most recent task in the active file.
|
||||
sub active_workers {
|
||||
my ($new_upid) = @_;
|
||||
|
||||
my $lkfn = "/var/log/pve/tasks/.active.lock";
|
||||
|
||||
my $timeout = 10;
|
||||
|
||||
my $code = sub {
|
||||
|
||||
my $tasklist = PVE::INotify::read_file('active');
|
||||
|
||||
my @ta;
|
||||
my $tlist = [];
|
||||
my $thash = {}; # only list task once
|
||||
|
||||
my $check_task = sub {
|
||||
my ($task) = @_;
|
||||
|
||||
my $pstart = PVE::ProcFSTools::read_proc_starttime($task->{pid});
|
||||
if ($pstart && ($pstart == $task->{pstart})) {
|
||||
push @$tlist, $task;
|
||||
} else {
|
||||
delete $task->{pid};
|
||||
push @ta, $task;
|
||||
}
|
||||
delete $task->{pstart};
|
||||
};
|
||||
|
||||
foreach my $task (@$tasklist) {
|
||||
my $upid = $task->{upid};
|
||||
next if $thash->{$upid};
|
||||
$thash->{$upid} = $task;
|
||||
&$check_task($task);
|
||||
}
|
||||
|
||||
if ($new_upid && !(my $task = $thash->{$new_upid})) {
|
||||
$task = PVE::Tools::upid_decode($new_upid);
|
||||
$task->{upid} = $new_upid;
|
||||
$thash->{$new_upid} = $task;
|
||||
&$check_task($task);
|
||||
}
|
||||
|
||||
|
||||
@ta = sort { $b->{starttime} cmp $a->{starttime} } @ta;
|
||||
|
||||
my $save = defined($new_upid);
|
||||
|
||||
foreach my $task (@ta) {
|
||||
next if $task->{endtime};
|
||||
$task->{endtime} = time();
|
||||
$task->{status} = PVE::Tools::upid_read_status($task->{upid});
|
||||
$save = 1;
|
||||
}
|
||||
|
||||
my $archive = '';
|
||||
my @arlist = ();
|
||||
foreach my $task (@ta) {
|
||||
if (!$task->{saved}) {
|
||||
$archive .= sprintf("$task->{upid} %08X $task->{status}\n", $task->{endtime});
|
||||
$save = 1;
|
||||
push @arlist, $task;
|
||||
$task->{saved} = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ($archive) {
|
||||
my $size = 0;
|
||||
my $filename = "/var/log/pve/tasks/index";
|
||||
eval {
|
||||
my $fh = IO::File->new($filename, '>>', 0644) ||
|
||||
die "unable to open file '$filename' - $!\n";
|
||||
PVE::Tools::safe_print($filename, $fh, $archive);
|
||||
$size = -s $fh;
|
||||
close($fh) ||
|
||||
die "unable to close file '$filename' - $!\n";
|
||||
};
|
||||
my $err = $@;
|
||||
if ($err) {
|
||||
syslog('err', $err);
|
||||
foreach my $task (@arlist) { # mark as not saved
|
||||
$task->{saved} = 0;
|
||||
}
|
||||
}
|
||||
my $maxsize = 50000; # about 1000 entries
|
||||
if ($size > $maxsize) {
|
||||
rename($filename, "$filename.1");
|
||||
}
|
||||
}
|
||||
|
||||
# we try to reduce the amount of data
|
||||
# list all running tasks and task and a few others
|
||||
# try to limit to 25 tasks
|
||||
my $ctime = time();
|
||||
my $max = 25 - scalar(@$tlist);
|
||||
foreach my $task (@ta) {
|
||||
last if $max <= 0;
|
||||
push @$tlist, $task;
|
||||
$max--;
|
||||
}
|
||||
|
||||
PVE::INotify::write_file('active', $tlist) if $save;
|
||||
|
||||
return $tlist;
|
||||
};
|
||||
|
||||
my $res = PVE::Tools::lock_file($lkfn, $timeout, $code);
|
||||
die $@ if $@;
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
# start long running workers
|
||||
# STDIN is redirected to /dev/null
|
||||
# STDOUT,STDERR are redirected to the filename returned by upid_decode
|
||||
# NOTE: we simulate running in foreground if ($self->{type} eq 'cli')
|
||||
sub fork_worker {
|
||||
my ($self, $dtype, $id, $user, $function) = @_;
|
||||
|
||||
$dtype = 'unknown' if !defined ($dtype);
|
||||
$id = '' if !defined ($id);
|
||||
|
||||
$user = 'root@pve' if !defined ($user);
|
||||
|
||||
my $sync = $self->{type} eq 'cli' ? 1 : 0;
|
||||
|
||||
local $SIG{INT} =
|
||||
local $SIG{QUIT} =
|
||||
local $SIG{PIPE} =
|
||||
local $SIG{TERM} = 'IGNORE';
|
||||
|
||||
my $starttime = time ();
|
||||
|
||||
my @psync = POSIX::pipe();
|
||||
my @csync = POSIX::pipe();
|
||||
|
||||
my $node = $self->{nodename};
|
||||
|
||||
my $cpid = fork();
|
||||
die "unable to fork worker - $!" if !defined($cpid);
|
||||
|
||||
my $workerpuid = $cpid ? $cpid : $$;
|
||||
|
||||
my $pstart = PVE::ProcFSTools::read_proc_starttime($workerpuid) ||
|
||||
die "unable to read process start time";
|
||||
|
||||
my $upid = PVE::Tools::upid_encode ({
|
||||
node => $node, pid => $workerpuid, pstart => $pstart,
|
||||
starttime => $starttime, type => $dtype, id => $id, user => $user });
|
||||
|
||||
my $outfh;
|
||||
|
||||
if (!$cpid) { # child
|
||||
|
||||
$0 = "task $upid";
|
||||
|
||||
$SIG{INT} = $SIG{QUIT} = $SIG{TERM} = sub { die "received interrupt\n"; };
|
||||
|
||||
$SIG{CHLD} = $SIG{PIPE} = 'DEFAULT';
|
||||
|
||||
# set sess/process group - we want to be able to kill the
|
||||
# whole process group
|
||||
POSIX::setsid();
|
||||
|
||||
POSIX::close ($psync[0]);
|
||||
POSIX::close ($csync[1]);
|
||||
|
||||
$outfh = $sync ? $psync[1] : undef;
|
||||
|
||||
eval {
|
||||
PVE::INotify::inotify_close();
|
||||
|
||||
if (my $atfork = $self->{atfork}) {
|
||||
&$atfork();
|
||||
}
|
||||
|
||||
# same algorythm as used inside SA
|
||||
# STDIN = /dev/null
|
||||
my $fd = fileno (STDIN);
|
||||
close STDIN;
|
||||
POSIX::close(0) if $fd != 0;
|
||||
|
||||
die "unable to redirect STDIN - $!"
|
||||
if !open(STDIN, "</dev/null");
|
||||
|
||||
$outfh = PVE::Tools::upid_open($upid) if !$sync;
|
||||
|
||||
# redirect STDOUT
|
||||
$fd = fileno(STDOUT);
|
||||
close STDOUT;
|
||||
POSIX::close (1) if $fd != 1;
|
||||
|
||||
die "unable to redirect STDOUT - $!"
|
||||
if !open(STDOUT, ">&", $outfh);
|
||||
|
||||
STDOUT->autoflush (1);
|
||||
|
||||
# redirect STDERR to STDOUT
|
||||
$fd = fileno (STDERR);
|
||||
close STDERR;
|
||||
POSIX::close(2) if $fd != 2;
|
||||
|
||||
die "unable to redirect STDERR - $!"
|
||||
if !open(STDERR, ">&1");
|
||||
|
||||
STDERR->autoflush(1);
|
||||
};
|
||||
if (my $err = $@) {
|
||||
my $msg = "ERROR: $err";
|
||||
POSIX::write($psync[1], $msg, length ($msg));
|
||||
POSIX::close($psync[1]);
|
||||
POSIX::_exit(1);
|
||||
kill('KILL', $$);
|
||||
}
|
||||
|
||||
# sync with parent (signal that we are read)
|
||||
if ($sync) {
|
||||
print "$upid\n";
|
||||
} else {
|
||||
POSIX::write($psync[1], $upid, length ($upid));
|
||||
POSIX::close($psync[1]);
|
||||
}
|
||||
|
||||
my $readbuf = '';
|
||||
# sync with parent (wait until parent is ready)
|
||||
POSIX::read($csync[0], $readbuf, 4096);
|
||||
die "parent setup error\n" if $readbuf ne 'OK';
|
||||
|
||||
eval { &$function($upid); };
|
||||
my $err = $@;
|
||||
if ($err) {
|
||||
chomp $err;
|
||||
$err =~ s/\n/ /mg;
|
||||
syslog('err', $err);
|
||||
print STDERR "TASK ERROR: $err\n";
|
||||
POSIX::_exit(-1);
|
||||
} else {
|
||||
print STDERR "TASK OK\n";
|
||||
POSIX::_exit (0);
|
||||
}
|
||||
kill('KILL', $$);
|
||||
}
|
||||
|
||||
# parent
|
||||
|
||||
POSIX::close ($psync[1]);
|
||||
POSIX::close ($csync[0]);
|
||||
|
||||
my $readbuf = '';
|
||||
# sync with child (wait until child starts)
|
||||
POSIX::read($psync[0], $readbuf, 4096);
|
||||
|
||||
if (!$sync) {
|
||||
POSIX::close($psync[0]);
|
||||
&$register_worker($cpid, $user, $upid);
|
||||
} else {
|
||||
chomp $readbuf;
|
||||
}
|
||||
|
||||
eval {
|
||||
die "got no worker upid - start worker failed\n" if !$readbuf;
|
||||
|
||||
if ($readbuf =~ m/^ERROR:\s*(.+)$/m) {
|
||||
die "starting worker failed: $1\n";
|
||||
}
|
||||
|
||||
if ($readbuf ne $upid) {
|
||||
die "got strange worker upid ('$readbuf' != '$upid') - start worker failed\n";
|
||||
}
|
||||
|
||||
if ($sync) {
|
||||
$outfh = PVE::Tools::upid_open($upid);
|
||||
}
|
||||
};
|
||||
my $err = $@;
|
||||
|
||||
if (!$err) {
|
||||
my $msg = 'OK';
|
||||
POSIX::write($csync[1], $msg, length ($msg));
|
||||
POSIX::close($csync[1]);
|
||||
|
||||
} else {
|
||||
POSIX::close($csync[1]);
|
||||
kill (9, $cpid); # make sure it gets killed
|
||||
die $err;
|
||||
}
|
||||
|
||||
PVE::Cluster::log_msg('info', $user, "starting task $upid");
|
||||
|
||||
my $tlist = active_workers($upid);
|
||||
PVE::Cluster::broadcast_tasklist($tlist);
|
||||
|
||||
my $res = 0;
|
||||
|
||||
if ($sync) {
|
||||
my $count;
|
||||
my $outbuf = '';
|
||||
eval {
|
||||
local $SIG{INT} =
|
||||
local $SIG{QUIT} =
|
||||
local $SIG{TERM} = sub { die "got interrupt\n"; };
|
||||
local $SIG{PIPE} = sub { die "broken pipe\n"; };
|
||||
|
||||
while (($count = POSIX::read($psync[0], $readbuf, 4096)) && ($count > 0)) {
|
||||
$outbuf .= $readbuf;
|
||||
while ($outbuf =~ s/^(([^\010\r\n]*)(\r|\n|(\010)+|\r\n))//s) {
|
||||
my $line = $1;
|
||||
my $data = $2;
|
||||
if ($data =~ m/^TASK OK$/) {
|
||||
# skip
|
||||
} elsif ($data =~ m/^TASK ERROR: (.+)$/) {
|
||||
print STDERR "$1\n";
|
||||
} else {
|
||||
print $line;
|
||||
}
|
||||
if ($outfh) {
|
||||
print $outfh $line;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
my $err = $@;
|
||||
|
||||
POSIX::close($psync[0]);
|
||||
|
||||
if ($outbuf) { # just to be sure
|
||||
print $outbuf;
|
||||
if ($outfh) {
|
||||
print $outfh $outbuf;
|
||||
}
|
||||
}
|
||||
|
||||
if ($err) {
|
||||
$err =~ s/\n/ /mg;
|
||||
print STDERR "$err\n";
|
||||
if ($outfh) {
|
||||
print $outfh "TASK ERROR: $err\n";
|
||||
}
|
||||
kill (15, $cpid);
|
||||
|
||||
} else {
|
||||
kill (9, $cpid); # make sure it gets killed
|
||||
}
|
||||
|
||||
close($outfh);
|
||||
|
||||
waitpid ($cpid, 0);
|
||||
$res = $?;
|
||||
&$log_task_result($upid, $user, $res);
|
||||
}
|
||||
|
||||
return wantarray ? ($upid, $res) : $upid;
|
||||
}
|
||||
|
||||
1;
|
273
README
Normal file
273
README
Normal file
@ -0,0 +1,273 @@
|
||||
User Management and Access Control
|
||||
==================================
|
||||
|
||||
Proxmox VE implements an easy but flexible way to manage users. A
|
||||
powerful Access Control algorithm is used to grant permissions to
|
||||
individual users or group of users.
|
||||
|
||||
Best Practices:
|
||||
|
||||
Use groups in ACLs (not individual users).
|
||||
|
||||
User Authentication
|
||||
===================
|
||||
|
||||
Proxmox VE can use different authentication servers. Those
|
||||
servers are listed in '/etc/pve/priv/domain.cfg', indexed by a unique
|
||||
ID (called 'authentication domain' or 'realm').
|
||||
|
||||
User names need to be unique. We create unique names by adding the
|
||||
'realm' to the user ID: <userid>@<realm>
|
||||
|
||||
File format 'domain.cfg'
|
||||
----example domains.cfg ------------------
|
||||
|
||||
# an active directory server
|
||||
AD: mycompany
|
||||
server1 10.10.10.1
|
||||
server2 10.10.10.2
|
||||
...
|
||||
|
||||
# an LDAP server
|
||||
LDAP: example.com
|
||||
server1 10.10.10.2
|
||||
....
|
||||
|
||||
------------------------------------------
|
||||
|
||||
There are 2 special authentication domains name 'pve' and 'pam':
|
||||
|
||||
* pve: stores paswords to "/etc/pve/priv/shadow.cfg" (SHA256 crypt);
|
||||
|
||||
* pam: use unix 'pam'
|
||||
|
||||
|
||||
Proposed user database fields:
|
||||
==============================
|
||||
|
||||
users:
|
||||
|
||||
login_name: email address (user@domain)
|
||||
enable: 1 = TRUE, 0 = FALSE
|
||||
expire: <integer> (account expiration date)
|
||||
domid: reference to authentication domain
|
||||
firstname: user first name
|
||||
lastname: user last name
|
||||
email: user's email address
|
||||
comment: arbitrary comment
|
||||
|
||||
special user root: The root user has full administrative privileges
|
||||
|
||||
group:
|
||||
|
||||
group_name: the name of the group
|
||||
user_list: list of login names
|
||||
comment: a more verbose description
|
||||
|
||||
privileges:
|
||||
|
||||
defines rights required to execute actions or read
|
||||
information.
|
||||
|
||||
VM.Allocate: create/remove new VM to server inventory
|
||||
VM.Migrate: migrate VM to alternate server on cluster
|
||||
VM.PowerMgmt: power management (start, stop, reset, shutdown, ...)
|
||||
VM.Console: console access to VM
|
||||
VM.Audit: view VM config
|
||||
VM.Modify: modify VM config
|
||||
|
||||
Datastore.Allocate: create/remove/modify a data store.
|
||||
Datastore.AllocateSpace: allocate space on a datastore
|
||||
Datastore.Audit: view/browse a datastore
|
||||
|
||||
Permissions.Modify: modify access permissions
|
||||
|
||||
Sys.PowerMgmt: Node power management (start, stop, reset, shutdown, ...)
|
||||
Sys.Console: console access to Node
|
||||
Sys.Syslog: view Syslog
|
||||
Sys.Audit: view node status/config
|
||||
|
||||
|
||||
We may need to refine those in future - the following privs
|
||||
are just examples:
|
||||
|
||||
VM.Create: create new VM to server inventory
|
||||
VM.Remove: remove VM from inventory
|
||||
VM.MemoryModify: modify memory associated with VM
|
||||
VM.AddNewDisk: add new disk to VM
|
||||
VM.AddExistingDisk: add an existing disk to VM
|
||||
VM.DiskModify: modify disk space for associated VM
|
||||
VM.UseRawDevice: associate a raw device with VM
|
||||
VM.PowerOn: power on VM
|
||||
VM.PowerOff: power off VM
|
||||
VM.ConfigureCD: assign a device/image file to VM
|
||||
VM.CpuModify: modify number of CPUs associated with VM
|
||||
VM.CpuCyclesModify: modify CPU cycles for VM
|
||||
VM.NetworkAdd: add network device to VM
|
||||
VM.NetworkConfigure: configure network device associated with VM
|
||||
VM.NetworkRemove: remove network device from VM
|
||||
|
||||
Network.AssignNetwork: assign system networks
|
||||
|
||||
role:
|
||||
|
||||
defines a sets of priviledges
|
||||
|
||||
predefined roles:
|
||||
|
||||
administrator: full administrative privileges
|
||||
read_only: read only
|
||||
no_access: no privileges
|
||||
|
||||
We store the following attribute for roles:
|
||||
|
||||
role_name: the name of the group
|
||||
description: a more verbose description
|
||||
privileges: list of privileges
|
||||
|
||||
permission:
|
||||
|
||||
Assign roles to users or groups.
|
||||
|
||||
|
||||
ACL and Objects:
|
||||
================
|
||||
|
||||
An access control list (ACL) is a list of permissions attached to an object. The list specifies who or what is allowed to access the object and what operations are allowed to be performed on the object.
|
||||
|
||||
Object: A Virtual machine, Network (bridge, venet), Hosts, Host Memory, Storage, ...
|
||||
|
||||
We can identify our objects by an unique (file system like) path, which also defines a tree like hierarchy relation. ACL can be inherited. Permissions are inherited if the propagate flag is set on the parent. Child permissions always overwrite inherited permissions. User permission takes precedence over all group permissions. If multiple group permission apply the resulting role is the union of all those group priviledges.
|
||||
|
||||
There is at most one object permission per user or group
|
||||
|
||||
We store the following attributes for ACLs:
|
||||
|
||||
propagate: propagate permissions down in the hierarchy
|
||||
path: path to uniquely identify the object
|
||||
user_or_group: ID of user or group (group ID start with @)
|
||||
role: list of role IDs.
|
||||
|
||||
User Database:
|
||||
|
||||
To keep it simple, we suggest to use a single text file, which is replicated to all cluster nodes.
|
||||
|
||||
Also, we can store ACLs inside this file.
|
||||
|
||||
Here is a short example how such file could look like:
|
||||
|
||||
-----User/Group/Role Database example--------
|
||||
|
||||
user:joe@example.com:$1$nd91DtDy$mJtzWJAN2AAABKij0JgMy1/:Joe Average:Just a comment:
|
||||
user:max@example.com:$1$nd91DtDy$LANSNJAN2AAABKidhfgMy3/:Max Mustermann:Another comment:
|
||||
user:edward@example.com:$1$nd91DtDy$LANSNAAAAAAABKidhfgMy3/:Edward Example:Example VM Manager:
|
||||
|
||||
group:admin:Internal Administrator Group:root:
|
||||
group:audit:Read only accounts used for audit::
|
||||
group:customers:Our Customers:joe@example.com,max@example.com:
|
||||
|
||||
role:vm_user:Virtual Machine User:VM.ConfigureCD,VM.Console:
|
||||
role:vm_manager:Virtual Machine Manager:VM.ConfigureCD,VM.Console,VM.AddNewDisk,VM.PowerOn,VM.PowerOff:
|
||||
role:vm_operator:Virtual Machine Operator:VM.Create,VM.ConfigureCD,VM.Console,VM.AddNewDisk,VM.PowerOn,VM.PowerOff:
|
||||
role:ds_consumer:DataStore Consumer:Datastore.AllocateSpace:
|
||||
role:nw_consumer:Network Consumer:Network.AssignNetwork:
|
||||
|
||||
# group admin can do anything
|
||||
acl:0:/:@admin:Administrator:
|
||||
# group audit can view anything
|
||||
acl:1:/:@audit:read_only:
|
||||
|
||||
# user max can manage all qemu/kvm machines
|
||||
acl:1:/vm/qemu:max@example.com:vm_manager:
|
||||
|
||||
# user joe can use openvz vm 230
|
||||
acl:1:/vm/openvz/230:joe@example.com:vm_user:
|
||||
|
||||
# user Edward can create openvz VMs using vmbr0 and store0
|
||||
acl:1:/vm/openvz:edward@example.com:vm_operator:
|
||||
acl:1:/network/vmbr0:edward@example.com:ds_consumer:
|
||||
acl:1:/storage/store0:edward@example.com:nw_consumer:
|
||||
|
||||
---------------------------------------------
|
||||
|
||||
Basic model RBAC -> http://en.wikipedia.org/wiki/Role-based_access_control
|
||||
|
||||
# Subject: A person or automated agent
|
||||
subject:joe@example.com:
|
||||
subject:max@example.com:
|
||||
|
||||
# Role: Job function or title which defines an authority level
|
||||
role:vm_user:Virtual Machine User:
|
||||
role:admin:Administrator:
|
||||
|
||||
# Subject Assignment: Subject -> Role(s)
|
||||
SA:vm_user:joe@example.com,max@example.com:
|
||||
SA:admin:joe@example.com:
|
||||
|
||||
# Permissions: An approval of a mode of access to a resource
|
||||
# Permission Assignment: Role -> Permissions (set of allowed operation)
|
||||
perm:vm_user:VM.ConfigureCD,VM.Console:
|
||||
perm:admin:VM.ConfigureCD,VM.Console,VM.Create:
|
||||
|
||||
---------------------------------------------
|
||||
|
||||
We can merge 'perm' into the 'role' table, because it is
|
||||
a 1 -> 1 mapping
|
||||
|
||||
subject:joe@example.com:
|
||||
subject:max@example.com:
|
||||
|
||||
role:vm_user:Virtual Machine User:VM.ConfigureCD,VM.Console:
|
||||
role:admin:Administrator:VM.ConfigureCD,VM.Console,VM.Create:
|
||||
|
||||
SA:vm_user:joe@example.com,max@example.com:
|
||||
SA:admin:joe@example.com:
|
||||
|
||||
-----------------------------------------------
|
||||
|
||||
We can have different subject assignment for different objects.
|
||||
|
||||
subject:joe@example.com:
|
||||
subject:max@example.com:
|
||||
|
||||
role:vm_user:Virtual Machine User:VM.ConfigureCD,VM.Console:
|
||||
role:admin:Administrator:VM.ConfigureCD,VM.Console,VM.Create:
|
||||
|
||||
# joe is 'admin' for openvz VMs, but 'vm_user' for qemu VMs
|
||||
SA:/vm/openvz:admin:joe@example.com:
|
||||
SA:/vm/qemu:vm_user:joe@example.com,max@example.com:
|
||||
|
||||
-----------------------------------------------
|
||||
|
||||
Let us use more convenient names.
|
||||
Use 'user' instead of 'subject'.
|
||||
Use 'acl' instead of 'SA'.
|
||||
|
||||
user:joe@example.com:
|
||||
user:max@example.com:
|
||||
|
||||
role:vm_user:Virtual Machine User:VM.ConfigureCD,VM.Console:
|
||||
role:admin:Administrator:VM.ConfigureCD,VM.Console,VM.Create:
|
||||
|
||||
# joe is 'admin' for openvz VMs, but 'vm_user' for qemu VMs
|
||||
acl:/vm/openvz:admin:joe@example.com:
|
||||
acl:/vm/qemu:vm_user:joe@example.com,max@example.com:
|
||||
|
||||
-----------------------------------------------
|
||||
|
||||
Finally introduce groups to group users. ACL can then
|
||||
use 'users' or 'groups'.
|
||||
|
||||
user:joe@example.com:
|
||||
user:max@example.com:
|
||||
|
||||
group:customers:Our Customers:joe@example.com,max@example.com:
|
||||
|
||||
role:vm_user:Virtual Machine User:VM.ConfigureCD,VM.Console:
|
||||
role:admin:Administrator:VM.ConfigureCD,VM.Console,VM.Create:
|
||||
|
||||
acl:/vm/openvz:admin:joe@example.com:
|
||||
acl:/vm/qemu:vm_user:@customers:
|
||||
|
||||
|
||||
-----------------------------------------------
|
37
TODO
Normal file
37
TODO
Normal file
@ -0,0 +1,37 @@
|
||||
TODO: pve-access-control
|
||||
------------------------
|
||||
|
||||
Seth?: Implement API Class to manage the domains.cfg file
|
||||
(AuthDomains.pm)
|
||||
|
||||
|
||||
pveum api:
|
||||
|
||||
Is it worth to emulate the useradd/usermod interface? We initially
|
||||
done that because we thought users are common with that.
|
||||
|
||||
But now it would be possible to expose a 'REST' like interface - like
|
||||
the one we use with pvesh.
|
||||
|
||||
pveum (get|set|create|delete) <path> [OPTIONS]
|
||||
|
||||
useradd: pveum create users/<username> [OPTIONS]
|
||||
usermod: pveum set users/<username> [OPTIONS]
|
||||
userdel: pveum delete users/<username>
|
||||
list: pveum get users
|
||||
data: pveum get users/<username>
|
||||
|
||||
groupadd: pveum create groups/<groupname> [OPTIONS]
|
||||
groupmod: pveum set groups/<groupname> [OPTIONS]
|
||||
groupdel: pveum delete groups/<groupname>
|
||||
list: pveum get groups
|
||||
data: pveum get groups/<groupname>
|
||||
|
||||
roleadd: pveum create roles/<rolename> [OPTIONS]
|
||||
rolemod: pveum set roles/<rolename> [OPTIONS]
|
||||
roledel: pveum delete roles/<rolename>
|
||||
list: pveum get roles
|
||||
data: pveum get roles/<rolename>
|
||||
|
||||
...
|
||||
|
14
changelog.Debian
Normal file
14
changelog.Debian
Normal file
@ -0,0 +1,14 @@
|
||||
libpve-access-control (1.0-1) unstable; urgency=low
|
||||
|
||||
* allow '-' in permission paths
|
||||
|
||||
* bump version to 1.0
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Mon, 27 Jun 2011 13:51:48 +0200
|
||||
|
||||
libpve-access-control (0.1) unstable; urgency=low
|
||||
|
||||
* first dummy package - no functionality
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Thu, 09 Jul 2009 16:03:00 +0200
|
||||
|
10
control.in
Normal file
10
control.in
Normal file
@ -0,0 +1,10 @@
|
||||
Package: libpve-access-control
|
||||
Version: @@VERSION@@-@@PKGRELEASE@@
|
||||
Section: perl
|
||||
Priority: optional
|
||||
Architecture: @@ARCH@@
|
||||
Depends: libc6 (>= 2.3), perl (>= 5.6.0-16), libcrypt-openssl-rsa-perl, libcrypt-openssl-random-perl, libjson-xs-perl, libjson-perl, libterm-readline-gnu-perl,libnet-ldap-perl, libpve-common-perl, pve-cluster
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Description: Proxmox VE access control library
|
||||
This package contains the role based user management and access
|
||||
control function used by Proxmox VE.
|
16
copyright
Normal file
16
copyright
Normal file
@ -0,0 +1,16 @@
|
||||
Copyright (C) 2010 Proxmox Server Solutions GmbH
|
||||
|
||||
This software is written by Proxmox Server Solutions GmbH <support@proxmox.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
102
pveum
Executable file
102
pveum
Executable file
@ -0,0 +1,102 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
use strict;
|
||||
use Getopt::Long;
|
||||
use PVE::Tools qw(run_command);
|
||||
use PVE::Cluster;
|
||||
use PVE::SafeSyslog;
|
||||
use PVE::AccessControl;
|
||||
use File::Path qw(make_path remove_tree);
|
||||
use Term::ReadLine;
|
||||
use PVE::INotify;
|
||||
use PVE::RPCEnvironment;
|
||||
use PVE::API2::User;
|
||||
use PVE::API2::Group;
|
||||
use PVE::API2::Role;
|
||||
use PVE::API2::ACL;
|
||||
use PVE::API2::AccessControl;
|
||||
use PVE::JSONSchema qw(get_standard_option);
|
||||
use PVE::CLIHandler;
|
||||
|
||||
use base qw(PVE::CLIHandler);
|
||||
|
||||
use Data::Dumper; # fixme: remove
|
||||
|
||||
$ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin';
|
||||
|
||||
initlog('pveum');
|
||||
|
||||
#fixme: logging?
|
||||
|
||||
die "please run as root\n" if $> != 0;
|
||||
|
||||
PVE::INotify::inotify_init();
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment->init('cli');
|
||||
|
||||
$rpcenv->init_request();
|
||||
$rpcenv->set_language($ENV{LANG});
|
||||
$rpcenv->set_user('root@pam');
|
||||
|
||||
# autmatically generate the private key if it does not already exists
|
||||
PVE::Cluster::gen_auth_key();
|
||||
|
||||
my $read_password = sub {
|
||||
|
||||
# return $ENV{PVE_PW_TICKET} if defined($ENV{PVE_PW_TICKET});
|
||||
|
||||
my $term = new Term::ReadLine ('pveum');
|
||||
my $attribs = $term->Attribs;
|
||||
$attribs->{redisplay_function} = $attribs->{shadow_redisplay};
|
||||
my $input = $term->readline('Enter new password: ');
|
||||
my $conf = $term->readline('Retype new password: ');
|
||||
die "Passwords do not match.\n" if ($input ne $conf);
|
||||
return $input;
|
||||
};
|
||||
|
||||
my $cmddef = {
|
||||
ticket => [ 'PVE::API2::AccessControl', 'create_ticket', ['username'], undef,
|
||||
sub {
|
||||
my ($res) = @_;
|
||||
print "$res->{ticket}\n";
|
||||
}],
|
||||
useradd => [ 'PVE::API2::User', 'create_user', ['userid'] ],
|
||||
usermod => [ 'PVE::API2::User', 'update_user', ['userid'] ],
|
||||
userdel => [ 'PVE::API2::User', 'delete_user', ['userid'] ],
|
||||
|
||||
groupadd => [ 'PVE::API2::Group', 'create_group', ['groupid'] ],
|
||||
groupmod => [ 'PVE::API2::Group', 'update_group', ['groupid'] ],
|
||||
groupdel => [ 'PVE::API2::Group', 'delete_group', ['groupid'] ],
|
||||
|
||||
roleadd => [ 'PVE::API2::Role', 'create_role', ['roleid'] ],
|
||||
rolemod => [ 'PVE::API2::Role', 'update_role', ['roleid'] ],
|
||||
roledel => [ 'PVE::API2::Role', 'delete_role', ['roleid'] ],
|
||||
|
||||
aclmod => [ 'PVE::API2::ACL', 'update_acl', ['path', 'roles'], { delete => 0 }],
|
||||
acldel => [ 'PVE::API2::ACL', 'update_acl', ['path', 'roles'], { delete => 1 }],
|
||||
};
|
||||
|
||||
my $cmd = shift;
|
||||
|
||||
if ($cmd && $cmd eq 'verifyapi') {
|
||||
PVE::RESTHandler::validate_method_schemas();
|
||||
exit 0;
|
||||
}
|
||||
|
||||
PVE::CLIHandler::handle_cmd($cmddef, "pveum", $cmd, \@ARGV, $read_password);
|
||||
|
||||
exit 0;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
pveum - PVE User Manager
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
pveum <COMMAND> [OPTIONS]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
no description available
|
22
test.pl
Executable file
22
test.pl
Executable file
@ -0,0 +1,22 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
use strict;
|
||||
use PVE::AccessControl;
|
||||
|
||||
# create ticket using username and password
|
||||
#my $ticket = PVE::AccessControl::create_ticket(undef, $username, $password);
|
||||
|
||||
# create ticket using ident auth
|
||||
my $login = getpwuid($<);
|
||||
my $username = ($< == 0) ? 'root' : "$login\@localhost";
|
||||
my $ticket = PVE::AccessControl::create_ticket(undef, $username);
|
||||
print "got ticket using ident auth: $ticket\n";
|
||||
|
||||
for (my $i = 0; $i < 1; $i++) {
|
||||
$ticket = PVE::AccessControl::create_ticket($ticket, $username);
|
||||
print "renewed ticket: $ticket\n";
|
||||
}
|
||||
|
||||
my $user = 'testuser';
|
||||
|
||||
PVE::AccessControl::add_user($ticket, $user, 'testpw');
|
10
test/Makefile
Normal file
10
test/Makefile
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
all:
|
||||
|
||||
.PHONY: check
|
||||
check:
|
||||
perl -I.. perm-test1.pl
|
||||
perl -I.. perm-test2.pl
|
||||
perl -I.. perm-test3.pl
|
||||
perl -I.. perm-test4.pl
|
||||
|
23
test/auth-test.pl
Normal file
23
test/auth-test.pl
Normal file
@ -0,0 +1,23 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
use strict;
|
||||
use Term::ReadLine;
|
||||
use PVE::AccessControl;
|
||||
|
||||
my $username = shift;
|
||||
die "Username missing" if !$username;
|
||||
sub read_password {
|
||||
|
||||
my $term = new Term::ReadLine ('pveum');
|
||||
my $attribs = $term->Attribs;
|
||||
$attribs->{redisplay_function} = $attribs->{shadow_redisplay};
|
||||
my $input = $term->readline('password: ');
|
||||
return $input;
|
||||
}
|
||||
|
||||
my $password = read_password();
|
||||
PVE::AccessControl::authenticate_user($username,$password);
|
||||
|
||||
print "Authentication Successful!!\n";
|
||||
|
||||
exit (0);
|
42
test/dump-perm.pl
Executable file
42
test/dump-perm.pl
Executable file
@ -0,0 +1,42 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
use strict;
|
||||
use PVE::AccessControl;
|
||||
use Getopt::Long;
|
||||
use Data::Dumper;
|
||||
|
||||
# example:
|
||||
# dump-perm.pl -f myuser.cfg root /
|
||||
|
||||
my $opt_file;
|
||||
if (!GetOptions ("file=s" => \$opt_file)) {
|
||||
exit (-1);
|
||||
}
|
||||
|
||||
my $username = shift;
|
||||
my $path = shift;
|
||||
|
||||
if (!($username && $path)) {
|
||||
print "usage: $0 <username> <path>\n";
|
||||
exit (-1);
|
||||
}
|
||||
|
||||
my $cfg;
|
||||
|
||||
if ($opt_file) {
|
||||
|
||||
my $fh = IO::File->new ($opt_file, 'r') ||
|
||||
die "can't open file $opt_file - $!\n";
|
||||
|
||||
$cfg = PVE::AccessControl::parse_config ($opt_file, $fh);
|
||||
$fh->close();
|
||||
|
||||
} else {
|
||||
$cfg = PVE::AccessControl::load_user_config();
|
||||
}
|
||||
my $perm = PVE::AccessControl::permission($cfg, $username, $path);
|
||||
|
||||
print "permission for user '$username' on '$path':\n";
|
||||
print join(',', keys %$perm) . "\n";
|
||||
|
||||
exit (0);
|
13
test/dump-users.pl
Executable file
13
test/dump-users.pl
Executable file
@ -0,0 +1,13 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
use strict;
|
||||
use PVE::AccessControl;
|
||||
use Data::Dumper;
|
||||
|
||||
my $cfg;
|
||||
|
||||
$cfg = PVE::AccessControl::load_user_config();
|
||||
|
||||
print Dumper($cfg) . "\n";
|
||||
|
||||
exit (0);
|
65
test/perm-test1.pl
Executable file
65
test/perm-test1.pl
Executable file
@ -0,0 +1,65 @@
|
||||
#!/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 = "user.cfg.ex1";
|
||||
$rpcenv->init_request(userconfig => $cfgfn);
|
||||
|
||||
sub check_roles {
|
||||
my ($user, $path, $expected_result) = @_;
|
||||
|
||||
my @ra = PVE::AccessControl::roles($rpcenv->{user_cfg}, $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";
|
||||
}
|
||||
|
||||
sub check_permission {
|
||||
my ($user, $path, $expected_result) = @_;
|
||||
|
||||
my $perm = PVE::AccessControl::permission($rpcenv->{user_cfg}, $user, $path);
|
||||
my $res = join(',', sort keys %$perm);
|
||||
|
||||
die "unexpected result\nneed '${expected_result}'\ngot '$res'\n"
|
||||
if $res ne $expected_result;
|
||||
|
||||
$perm = $rpcenv->permissions($user, $path);
|
||||
$res = join(',', sort keys %$perm);
|
||||
die "unexpected result (compiled)\nneed '${expected_result}'\ngot '$res'\n"
|
||||
if $res ne $expected_result;
|
||||
|
||||
print "PERM:$path:$user:$res\n";
|
||||
|
||||
}
|
||||
|
||||
check_roles('max@pve', '/', '');
|
||||
check_roles('max@pve', '/vms', 'vm_admin');
|
||||
|
||||
#user permissions overrides group permissions
|
||||
check_roles('max@pve', '/vms/100', 'customer');
|
||||
check_roles('max@pve', '/vms/101', 'vm_admin');
|
||||
|
||||
check_permission('max@pve', '/', '');
|
||||
check_permission('max@pve', '/vms', 'Permissions.Modify,VM.Allocate,VM.Audit,VM.Console');
|
||||
check_permission('max@pve', '/vms/100', 'VM.Audit,VM.PowerMgmt');
|
||||
|
||||
check_permission('alex@pve', '/vms', '');
|
||||
check_permission('alex@pve', '/vms/100', 'VM.Audit,VM.PowerMgmt');
|
||||
|
||||
|
||||
check_roles('max@pve', '/vms/200', 'storage_manager');
|
||||
check_roles('joe@pve', '/vms/200', 'vm_admin');
|
||||
check_roles('sue@pve', '/vms/200', '');
|
||||
|
||||
print "all tests passed\n";
|
||||
|
||||
exit (0);
|
39
test/perm-test2.pl
Executable file
39
test/perm-test2.pl
Executable file
@ -0,0 +1,39 @@
|
||||
#!/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 = "test2.cfg";
|
||||
$rpcenv->init_request(userconfig => $cfgfn);
|
||||
|
||||
sub check_roles {
|
||||
my ($user, $path, $expected_result) = @_;
|
||||
|
||||
my @ra = PVE::AccessControl::roles($rpcenv->{user_cfg}, $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";
|
||||
}
|
||||
|
||||
# inherit multiple group permissions
|
||||
|
||||
check_roles('User1@pve', '/', '');
|
||||
check_roles('User2@pve', '/', '');
|
||||
|
||||
check_roles('User1@pve', '/vms', 'Role1,Role2');
|
||||
check_roles('User2@pve', '/vms', '');
|
||||
|
||||
check_roles('User1@pve', '/vms/100', 'Role1,Role2');
|
||||
check_roles('User2@pve', '/vms', '');
|
||||
|
||||
print "all tests passed\n";
|
||||
|
||||
exit (0);
|
34
test/perm-test3.pl
Executable file
34
test/perm-test3.pl
Executable file
@ -0,0 +1,34 @@
|
||||
#!/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 = "test3.cfg";
|
||||
$rpcenv->init_request(userconfig => $cfgfn);
|
||||
|
||||
sub check_roles {
|
||||
my ($user, $path, $expected_result) = @_;
|
||||
|
||||
my @ra = PVE::AccessControl::roles($rpcenv->{user_cfg}, $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('User1@pve', '/vms/300', 'Role1');
|
||||
check_roles('User1@pve', '/vms/200', 'Role2');
|
||||
|
||||
print "all tests passed\n";
|
||||
|
||||
exit (0);
|
32
test/perm-test4.pl
Executable file
32
test/perm-test4.pl
Executable file
@ -0,0 +1,32 @@
|
||||
#!/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 = "test4.cfg";
|
||||
$rpcenv->init_request(userconfig => $cfgfn);
|
||||
|
||||
sub check_roles {
|
||||
my ($user, $path, $expected_result) = @_;
|
||||
|
||||
my @ra = PVE::AccessControl::roles($rpcenv->{user_cfg}, $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/300', 'Role1');
|
||||
check_roles('User2@pve', '/vms/300', '');
|
||||
|
||||
print "all tests passed\n";
|
||||
|
||||
exit (0);
|
11
test/test2.cfg
Normal file
11
test/test2.cfg
Normal file
@ -0,0 +1,11 @@
|
||||
user:User1@pve:1:
|
||||
user:User2@pve:1:
|
||||
|
||||
group:GroupA:User1@pve:
|
||||
group:GroupB:User1@pve:
|
||||
|
||||
role:Role1:VM.PowerMgmt:
|
||||
role:Role2:VM.Console:
|
||||
|
||||
acl:1:/vms:@GroupA:Role1:
|
||||
acl:1:/vms:@GroupB:Role2:
|
11
test/test3.cfg
Normal file
11
test/test3.cfg
Normal file
@ -0,0 +1,11 @@
|
||||
user:User1@pve:1:
|
||||
user:User2@pve:1:
|
||||
|
||||
group:GroupA:User1@pve:
|
||||
group:GroupB:User1@pve:
|
||||
|
||||
role:Role1:VM.PowerMgmt:
|
||||
role:Role2:VM.Console:
|
||||
|
||||
acl:1:/vms:@GroupA:Role1:
|
||||
acl:1:/vms/200:@GroupB:Role2:
|
11
test/test4.cfg
Normal file
11
test/test4.cfg
Normal file
@ -0,0 +1,11 @@
|
||||
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:
|
||||
|
||||
acl:1:/vms:@GroupA:Role1:
|
||||
acl:1:/vms:User2@pve:NoAccess:
|
22
test/user.cfg.ex1
Normal file
22
test/user.cfg.ex1
Normal file
@ -0,0 +1,22 @@
|
||||
user:joe@pve:1:
|
||||
user:max@pve:1:
|
||||
user:alex@pve:1:
|
||||
user:sue@pve:1:
|
||||
user:carol@pam:1:
|
||||
|
||||
group:testgroup1:joe@pve,max@pve,sue@pve:
|
||||
group:testgroup2:alex@pve,carol@pam,sue@pve:
|
||||
group:testgroup3:max@pve:
|
||||
|
||||
role:storage_manager:Datastore.AllocateSpace,Datastore.Audit:
|
||||
role:customer:VM.Audit,VM.PowerMgmt:
|
||||
role:vm_admin:VM.Audit,VM.Allocate,Permissions.Modify,VM.Console:
|
||||
|
||||
acl:1:/vms:@testgroup1:vm_admin:
|
||||
acl:1:/vms/100/:alex@pve,max@pve:customer:
|
||||
acl:1:/storage/nfs1:@testgroup2:storage_manager:
|
||||
acl:1:/users:max@pve:Administrator:
|
||||
|
||||
acl:1:/vms/200:@testgroup3:storage_manager:
|
||||
acl:1:/vms/200:@testgroup2:NoAccess:
|
||||
|
8
user.cfg.ex
Normal file
8
user.cfg.ex
Normal file
@ -0,0 +1,8 @@
|
||||
user:joe@localhost:1:
|
||||
|
||||
group:testgroup:joe@localhost:
|
||||
|
||||
role:admin:VM.ConfigureCD,VM.Create,Permissions.Modify,VM.Console:
|
||||
|
||||
acl:0:/users:@testgroup,joe@localhost:Administrator:
|
||||
|
Loading…
Reference in New Issue
Block a user