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 :|