On Thu, Nov 24, 2011 at 09:11:20AM +0000, Daniel P. Berrange wrote: > On Wed, Nov 23, 2011 at 06:17:46PM +0100, Michal Privoznik wrote: > > Hi all, > > > > I'd like to implement this new feature for libvirt. However, I think we > > should settle down on design first. My biggest concern is choosing the > > right level on on which ACLs will be implemented. Should be interested > > only in (user, API), or with more granularity (user, API, API's parameters)? > > Or should we take the RBAC path? > > How should we even identify and authorize users? > > > > My initial though is to create framework which can be used then to > > implement ACLs on any level we want. > > > > What's our opinion? > > I have been working on the plan & investigating various implementation > details of this myself over the past few months. Here is the initial > design mail I had on the subject: > > https://www.redhat.com/archives/libvir-list/2011-June/msg00244.html Here is a more detailed technical plan of what I intended to implement. Technical plan for implementing RBAC with integration to sVirt ============================================================== What follows is my current design for implementing fine grained access control in libvirt drivers. The intent is to provide RBAC, with an implementation going via sVirt, and another native libvirt implementation based on plain config files. It will also be able to replicate the existing VIR_CONNECT_RO access control checks. The intent of the design is to be comprehensive enough to enable SELinux to be used as the exclusive access controller across libvirt, without need of other control mechanism. eg, so you can write a policy for 'virt-top' which strictly confines what it can do with libvirt. I expect that the primary users of the SELinux capability will be application designers, or a few end user administrators with very high security needs. I expect that most end user adminsitrators will just use the simple access controller with its plain config files. Identity representation ======================= We need to be able to store a minimum of: - UNIX user name; - UNIX group name; - SASL username - x509 distinguished name - SELinux context potentially extended in the future Example structure enum { VIR_IDENTITY_ATTR_UNIX_USER_NAME, VIR_IDENTITY_ATTR_UNIX_GROUP_NAME, VIR_IDENTITY_ATTR_SASL_USER_NAME, VIR_IDENTITY_ATTR_X509_DISTINGUISHED_NAME, VIR_IDENTITY_ATTR_SELINUX_CONTEXT, }; typedef struct _virIdentity virIdentityAttr; struct _virIdentityAttr { int type; char *value; }; typedef struct _virIdentity virIdentity; struct _virIdentity { int nattrs; virIdentityAttr *attrs; }; Identity association ==================== Need to be able to associate an identity with a virConnectPtr instance. The initial identity is determined during the initial connection handshake and authentication process, by libvirtd. The client may provide an alternative identity in addition to the initial identity, which is what is used for actual operation checks. eg, there is identity to "authorize as" and an identity to "act as". initially both are the same identity. For a client to be able to set an alternative "act as" identity requires a permission check against the "authorize as" identity. Need a new API for opening a connection which includes an identity virConnectOpenIdentity(const char *name, virConnectAuthPtr auth, virIdentityPtr identity, unsigned int flags); When this is invoked on the libvirtd side, 'identity' is providing the initial "authorize as" identity. In terms of RPC API calls from the client, this is just a shorthand for doing conn = virConnectOpenIdentity(name, auth, NULL, flags); virConnectSetIdentity(conn, identity); (see next descrition) When this is invoked on the client side, 'identity' is providing the alternative "act as" identity. The "authorize as" identity is determined by libvirtd. Typically clients will not use this. Only clients which are brokers for other clients (eg libvirt-CIM, libvirt-SNMP, libvirt-QMF) would use this capability. There is also a new API for changing the identity on the fly virConnectSetIdentity(virConnectPtr conn, virIdentityPtr identity, unsigned int flags); This always updates the "act as" identity. Again this is rarely used unless the client is a broker for other clients. There is a new API for retrieving the current identity enum { VIR_CONNECT_GET_IDENTITY_AUTH_AS = 0, VIR_CONNECT_GET_IDENTITY_ACT_AS = (1 << 0), }; virIdentityPtr virConnectGetIdentity(virConnectPtr conn, unsigned int flags); Access controllers ================== The access controllers will live in src/access/. As with security drivers it will be possible to stack access controllers. The main internal API will be provided by viraccessmanager.c - Entry point for driver calls viraccessdriver.h - Defines internal driver impl contract viraccessvectors.h - Defines the list of permissions that are checkable There will then be several internal driver implementations viraccessselinux.c - SELinux access controller viraccesssimple.c - Simply config file based access controller viraccessreadonly.c - Simulate current VIR_CONNECT_RO flag viraccessnop.c - Do not perform any access control checks viraccessstack.c - Stack a ordered list of access controllers The access manager API will have the following methods for creating instances of the managers: virAccessManagerPtr virAccessManagerNewStack(virAccessManagerPtr first, virAccessManagerPtr second) virAccessManagerPtr virAccessManagerNewByName(const char *name) (name=selinux|simple|readonly|nop) There will be methods for checking permissions (access vector) against object instances: bool virAccessManagerCheckDomainDef(virAccessManagerPtr mgr, virIdentityPtr identity, virDomainDefPtr domain, virAccessVectorDomain vector); bool virAccessManagerCheckNetworkDef(virAccessManagerPtr mgr, virIdentityPtr identity, virNetworkDefPtr network, virAccessVectorNetwork vector); bool virAccessManagerCheckStoragePoolDef(virAccessManagerPtr mgr, virIdentityPtr identity, virStoragePoolDefPtr pool, virAccessVectorStoragePool vector); ....etc for each object type... There will also need to be a catch all for cases where we need todo a permission check for which there is no corresponding object bool virAccessMangerCheckGeneric(virAccessManagerPtr mgr, virIdentityPtr identity, virAccessVectorGeneric vector); We might add other types of check later too, as we do increasingly fine grained checking, eg on paths, net devices, whatever. NB, we only return bool (accept,deny), not a tri-state (accept,deny,error) because as far as the clients are concerned, the deny+error states must be indistinguishable. The access vectors file will contain enums specifying all possible logical operations for each type of object that need controlling, eg typedef enum { VIR_ACCESS_VECTOR_DOMAIN_DEFINE, VIR_ACCESS_VECTOR_DOMAIN_REDEFINE, VIR_ACCESS_VECTOR_DOMAIN_START, VIR_ACCESS_VECTOR_DOMAIN_STOP, VIR_ACCESS_VECTOR_DOMAIN_VIEW, (eg can check existance vir virConnectListDomains) VIR_ACCESS_VECTOR_DOMAIN_READ, (eg can read XML & other properties) VIR_ACCESS_VECTOR_DOMAIN_READ_SECURE, (eg can request VIR_DOMAIN_XML_SECURE flag) ....many more... } virAccessVectorDomain; typedef enum { VIR_ACCESS_VECTOR_STORAGE_POOL_DEFINE, VIR_ACCESS_VECTOR_STORAGE_POOL_REDEFINE, ....many more... } virAccessVectorStoragePool; ...more for each other object type... Mapping to SELinux ================== The SELinux driver will work as follows - Object -> context mapping - if there is an SELinux context available in the object in question, that will be used. Otherwise the context of libvirtd itself will be used - Identity: The identity will be populated with the clients' SELinux context, as obtained from getpeercon(). This works trivially for UNIX sockets, or non-trivially requiring IPSec configuration, for TCP sockets - Object types: each libvirt object type will have a corresponding SELinux security class. - Access vector enums in libvirt will be converted to a string format via a traditional libvirt VIR_ENUM_IMPL. The SELinux policy will define access vectors with these matching names. The string will then be converted to an SELinux access_vector_t value using XXXXX (can't remember API name right now) - The actual permission check is done using avc_has_perm(subject, object, object class, access vector) The SELinux policy will determine whether any single check results in an audit log Mapping to Simple Driver ======================== The simple access controller will work from traditional user names in the identiy (unix user, unix group, sasl, x509 dname, etc). In terms of objects, it will either use the UUID or the name primarily, though might also use other attributes from the XML as required (paths, network device names, whatever) There will be some simple configuration file format for defining - roles - this groups together users who do the same job - objectsets - this groups together objects which are managed together - permissionsets - ths groups together access vectors which are used together - grants - list of triples (role,objectset, permissionset) which actually grant permissions Each grant entry will optionally have an 'audit' flag. If this is set, then any time the access is granted or denied will result in libvirt sending an audit log message. Checking in drivers =================== Only stateful drivers running inside libvirtd will be access controlled. It is meaningless to attempt to access control drivers running outside libvirtd, because any checks are trivially bypassed simply by not using libvirtd and connecting directly to VMWare VirtualCenter / whatever. All stateful drivers will have all APIs modified to include permission checks in all relevant places. Most of the time this will be upfront accept/deny type checks before any processing takes place, but in some APIs there will be filtering where the access control check is actually used to filter what data is returned to the client. eg virDomainListDomains will filter out guests the client is not allowed to view. This is the unbounded, ill-defined part of the work where the fun issues will be discovered.... As we go through each API, we need to figure out what access vectors need to be checked. There may be multiple vectors needing checks per API call. The return data may also need to be filtered. Proof of suitability ==================== In the course of implementing all this it will be desirable to have some applications and/or scenarios to use to proof that the resulting system is actually suitable for its purpose. I would suggest the following applications - virt-viewer (control console access) - virt-top (control stats access) And for a scenario - virsh, in a shared user ISP like model. eg multiple users connecting to same libvirtd (qemu:///system), but each only able to access their own VMs. -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :| -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list