XCP, RBAC and PAM authentication in the XenAPI

Many users have been asking a bit about the internals of the authentication mechanisms in XCP and XAPI, and how to add extra users, so I thought I should talk a bit about it.
Just after installation, XCP allows only root to log in, since by default this is the only user available over PAM/NSS in dom0. Extra users can be added locally to /etc/passwd or externally by using NSS/PAM extensions such as LDAP [1], NIS, winbind, Likewise etc in dom0. However, due to backwards-compatible behavior in the XenAPI that goes back to XenServer 5.0, all these users will still have root access by default, and therefore they will all be allowed access to all of the XenAPI calls.
In order to restrict the XenAPI calls for these extra users, it is necessary to make XAPI aware that an external authentication mechanism such as PAM is being used instead of the default backwards-compatible internal XAPI authentication. This can be accomplished by the following xe cli command, which will reroute all the XenAPI authentication requests to the external PAM XAPI plugin:

# xe pool-enable-external-auth auth-type=PAM service-name=<any-name>

A nice side-effect of enabling external authentication in XAPI as above is that all the XenAPI authentication requests will go through XAPI’s role-based access control (RBAC) mechanism. The RBAC in XAPI allows fine-tuning the permissions of any extra users or groups that are available over external authentication.
In XCP parlance, external users and groups are called “subjects”. By default all subjects will be denied access to the XenAPI. In order to allow any of them access to a subset (or the full set) of the XenAPI calls, two steps are necessary:
1. Add a subject to the subject-list in the XAPI database:

# xe subject-add subject-name=<external-user-or-group-name>

By default, a new subject will not be allowed to use any XenAPI calls.
2. Assign a set of XenAPI calls to the subject:

# xe subject-role-add role-name=<role-or-permission-name>

The pre-defined role names are pool-admin, pool-operator, vm-power-admin, vm-admin, vm-operator, read-only, which define a total order of permission sets ranging from all XenAPI calls (pool-admin) to only read-only calls (read-only). A relatively up-to-date table relating the available roles to permissions can be found at http://support.citrix.com/article/CTX126441.
Roles are only alias to a set of XenAPI calls. It is also possible to assign more than one XenAPI call to a subject. Therefore, it is possible to tweak the pre-defined roles by adding extra calls to it. For instance, to create a vm-operator that can also migrate VMs (whose XenAPI call is by default only available to pool-operator), you can do the following, assuming you have a subject called “migrate-op”:

# xe subject-role-add role-name=vm-operator uuid=<uuid-of-subject-"migrate-op">
# xe subject-role-add role-name=vm.pool_migrate uuid=<uuid-of-subject-"migrate-op">

You can then check what roles are assigned to what subjects:

# xe subject-list

The user root is still available for disaster-recovery purposes, in case either the external authentication mechanism stops working due to some problem in the external authentication plugin or the pool master’s subject list is not available due to some accident. In this case, slave emergency commands to recover the host or the pool can still use the user root.
Also, an interesting detail is that even though the XenAPI calls are usually available through the master only, you can call the XenAPI session.login_with_password(username,pwd) on any slave so that you can distribute the authentication load through all the hosts of the pool.
It is possible to list the available roles using the xe cli command

# xe role-list

but this will result in a simplified list of the default role names. In order to obtain the full list of all the available role names that can be added to a subject, you need to use the XenAPI directly, for instance using python:

$ python
>>> import xmlrpclib
>>> x=xmlrpclib.Server("https://my-xenserver-host")
>>> s=x.session.login_with_password("root","my-password")['Value']
>>> roles = x.role.get_all_records(s)['Value']
>>> print roles
{'Status': 'Success', 'Value': {'OpaqueRef:d7ea15b2-0664-1cd5-dc30-7c49a546ae80': {'subroles': [], 'name_description': 'A basic permission', 'name_label': 'pool.get_wlb_enabled', 'uuid': 'd7ea15b2-0664-1cd5-dc30-7c49a546ae80'}, ... ...
>>> role_names = map(lambda x: roles[x]["name_label"], roles)
>>> role_names.sort()
>>> print role_names

You can also find the same list of available roles by doing

cat /opt/xensource/debug/rbac_static.csv

in dom0.
References:
[1] For instance, for installing the openldap client packages as the PAM/NSS provider for XCP in dom0, one can use yum:

# yum --disablerepo=citrix --enablerepo=base install openldap openldap-clients nss_ldap cyrus-sasl-ldap cyrus-sasl-gssapi

and then manually configure the PAM config file at /etc/pam.d/xapi, adding the relevant pam_ldap.so entries, and /etc/nsswitch.conf, adding the relevant ldap entries according to the nss_ldap and pam_ldap help entries in the manpages or in several detailed tutorials that can be easily found on the Internet, such as in http://wiki.debian.org/LDAP/PAM and http://wiki.debian.org/LDAP/NSS .

Read more