Entitlements
Each REST API operation is authenticated to check if the user has the required privileges.
There is a plugin architecture to allow different entitlement mechanisms to be used.
A new entitlements checker implementation can be supplied by implementing
brooklyn.management.entitlement.EntitlementManager
.
User Entitlements
User entitlements can be set globally with the property:
brooklyn.webconsole.security.users=staff,itil
brooklyn.entitlements.global=org.apache.brooklyn.core.mgmt.entitlement.PerUserEntitlementManager
brooklyn.entitlements.perUser.staff=user
brooklyn.entitlements.perUser.itil=root
RBAC Entitlements
Each REST API operation (to list/view items, or to perform changes) is authenticated to check if the user has the required privileges.
There is a plugin architecture to allow different entitlement mechanisms to be used. One mechanism available is io.cloudsoft.amp.entitlements.rbac.PerRoleEntitlementManager. This allows plugins for the various decision points.
Note: these package names may change if the code moves to org.apache.brooklyn. Configuration
This reads from brooklyn.cfg, such as:
brooklyn.entitlements.global=io.cloudsoft.amp.rbac:io.cloudsoft.amp.entitlements.rbac.PerRoleEntitlementManager
io.cloudsoft.amp.entitlements.rbac.roleCacheExpiryDuration=15m
io.cloudsoft.amp.entitlements.rbac.userToRole=com.acme.amp.rbac:com.acme.amp.rbac.MyCustomRoleResolver
io.cloudsoft.amp.entitlements.rbac.perRole.adminstaff=root
io.cloudsoft.amp.entitlements.rbac.perRole.supportstaff=readonly
io.cloudsoft.amp.entitlements.rbac.perRole.automatons=minimal
io.cloudsoft.amp.entitlements.rbac.perRole.specialpeople=com.acme.amp.rbac.MyCustomEntitlements
The userToRole refers to a class of type io.cloudsoft.amp.entitlements.rbac.RoleResolver
, available in a bundle whose
symbolic name is com.acme.amp.rbac
, which maps from a user to the role(s) for that user. If a user is in multiple roles, then the user has permission if any of the roles grant that permission.
The roleCacheExpiryDuration is the duration that the roles of a user will be cached for. Note that an extreme (!) way to flush the cache is to “reload properties”, which will replace this EntitlementManager with a new instance.
The perRole has an entry per role name. This value can be to a pre-defined build-in role (i.e. “root”, “readonly” and “minimal”). Alternatively, it can point to a custom brooklyn.management.entitlement.EntitlementManager class. RoleResolver.
In addition to the perRole entries, it’s possible to define mapping between groups defined only as part of the ExplicitUsersAndRolesSecurityProvider.ROLES_FOR_USER
keys or only from the groups from LDAP. See example
An instance of the class will be instantiated reflectively. The constructor should have a signature that is one of:
(ManagementContext mgmt, AMPProperties properties)
(ManagementContext mgmt)
(AMPProperties properties)
()
If the class also implements {@link ManagementContextInjectable}
, then the management context will be injected immediately after construction.
Custom EntitlementManager
The RBAC configuration allows one to plugin a custom entitlement manager to be associated with a given role, to meet your exact needs.
The EntitlementManager interface has a single method: isEntitled. This is passed details of the what is being done, and to what, allowing a boolean to be returned to indicate if it is permitted.
LdapGroupsResolver
The class io.cloudsoft.amp.entitlements.rbac.LdapGroupsResolver
implements RoleResolver
and looks for the user roles inside the request EntitlementContext
Using io.cloudsoft.amp.security.DomainLocalSecurityProvider
or org.apache.brooklyn.rest.security.provider.LdapSecurityProvider
as Security Provider and the proper configuration will put on the EntitlementContext the
user LDAP groups mapped with the prefix passed to the config key brooklyn.webconsole.security.ldap.group_config_key
.
DomainLocalSecurityProvider example
DomainLocalSecurityProvider extends the functionality of the LdapSecurityProvider adding also the local definition of users and grops using alternatively ExplicitUsersAndRolesSecurityProvider when the LDAP authentication fails.
This example will use a hybrid set of user and entitlements, two users defined in the config file and their groups, and also the mapping for the groups for the LDAP user
# Local user names
brooklyn.webconsole.security.users=admin,readonlyPlusLogs
# Plain text password
brooklyn.webconsole.security.user.admin.password=password
brooklyn.webconsole.security.user.readonlyPlusLogs.password=password
# user "admin" group
brooklyn.webconsole.security.user.admin.groups=amp_administrators_local_group
# user "readonlyPlusLogs" groups
brooklyn.webconsole.security.user.readonlyPlusLogs.groups=readonly_local_group,log_viewer_group
# UI module for replace default browser login
brooklyn.webconsole.security.login.form=brooklyn-ui-login
brooklyn.webconsole.security.unauthenticated.endpoints=brooklyn-ui-login
# Security provider
brooklyn.webconsole.security.provider=io.cloudsoft.amp.security.DomainLocalSecurityProvider
# LDAP configuration. See LDAP conf
brooklyn.webconsole.security.ldap.url=ldap://<server>:389/
brooklyn.webconsole.security.ldap.realm=realm
brooklyn.webconsole.security.ldap.ou=OU
brooklyn.webconsole.security.ldap.fetch_user_group=true
brooklyn.webconsole.security.ldap.login_info_log=true
# AMP will ignore LDAP groups not mapped with the next key
brooklyn.webconsole.security.ldap.group_config_key=io.cloudsoft.amp.entitlements.rbac.perGroupLdapOnly
# Entitlement manager
brooklyn.entitlements.global=io.cloudsoft.amp.rbac:io.cloudsoft.amp.entitlements.rbac.PerRoleEntitlementManager
# Group Resolver
io.cloudsoft.amp.entitlements.rbac.userToRole=io.cloudsoft.amp.rbac:io.cloudsoft.amp.entitlements.rbac.LdapGroupsResolver
# Mapping for the LDAP groups amp_administrators_group, log_viewer_group and readonly_group
io.cloudsoft.amp.entitlements.rbac.perGroupLdapOnly.amp_administrators_group=root
io.cloudsoft.amp.entitlements.rbac.perGroupLdapOnly.readonly_group=readonly
# Mapping for the AMP groups amp_administrators_group, readonly_group and log_viewer_group
io.cloudsoft.amp.entitlements.rbac.perGroupAmpOnly.amp_administrators_local_group=root
io.cloudsoft.amp.entitlements.rbac.perGroupAmpOnly.readonly_local_group=readonly
# Valid mapping for groups defined in LDAP or in this file
io.cloudsoft.amp.entitlements.rbac.perRole.log_viewer_group=logViewer
This example user the ...perRole
entry to be mapped with the logViewer
entitlement for user on the LDAP group named “log_viewer_group” but also to the readonlyPlusLogs group defined in the same file.
LDAP Entitlements
AMP supports LDAP integration for entitlements - i.e. the entitlements rules are stored in LDAP.
To use this, you must set in your brooklyn.properties
:
# requires LDAP used for authorization
brooklyn.webconsole.ldap.url=ldap://LDAP_SERVER/
brooklyn.webconsole.ldap.realm=AMP
brooklyn.webconsole.ldap.password=PASSWORD
# and set the entitlements to be this implementation (or a subclass, if necessary)
brooklyn.entitlements.global=io.cloudsoft.amp.entitlements.LdapEntitlementManager
In the LDAP schema, this entitlements scheme requires a new objectClass,
which in this guide we will call acmePermission
, with the following attributes (all marked optional):
entityTagRegexesForNavigating
: means you can navigate to entities with any tag matching any regex (multi-valued LDAP attribute)entityTagRegexesForReading
: means you can see sensors+config on entities with any tag matching any regex (multi-valued LDAP attribute)entityTagRegexesForWriting
: means you can invoke effectors on entities with any tag matching any regex (multi-valued LDAP attribute)deployAllowed
: means you are allowed to deploy new applications (boolean / present or absent)serverInfoAllowed
: means you are allowed to see AMP information (boolean / present or absent)root
: means you are root, having all the permissions above and all others (boolean / present or absent)
The DIT will contain:
- an OU (groups) containing Posix Groups, where each
group
defines- zero or more
user
members - an
acmePermission
object, defining permissions for all members of the group
- zero or more
- an OU (users) containing User Accounts, where each
user
defines- the
password
attribute - an
acmePermission
object, defining specific permissions for that user (in addition to all permissions from all groups)
- the
This structure allows to have single User Account with (1) permissions defined specific to that user, and
(2) permissions defined on groups of which he/she is a member.
As is often recommended for entitlements, these are purely additive:
a user will be entitled to access anything which is entitled by any acmePermission
object on the user or any of his/her groups.
An example for (1) is:
- user1 has
entityTagRegexesForNavigating: acme.tenant.entity:${user}
attribute
while an example of (2) is:
administrators
group has 2 values for theentityTagRegexesForNavigating
attribute:acme.tenant.entity.master
andacme.tenant.entity:${user}
user2
is memeber ofadministrators
group so user2 inherits theacme.tenant.entity.master
to see the AMP blueprint andacme.tenant.entity:${user}
to see his own child AMP.
Please see the LDAP command reference section for instructions for configuring LDAP with the acmePermission
schema.
Use Cases
Using the LDAP structure described above with the new AMP feature to manage entitlements, it is possible to cover the following scenarios of interest:
Entitlements for master AMP and tenants/children AMP
In order to enforce entitlement on the master AMP, we need to add tags to the entities so that when we create
Tenant-Foo
AMP at master, we tag Tenant (AMPNode)
and Service (AMPMirror)
entities as acme.tenant.entity:${user}
(where ${user}
is replaced with the tenant name).
Also we add tag acme.tenant.entity.master
to the master blueprint so that all users can navigate through it
(to access their tenant) and so that controllers can invoke effectors there.
These tags are done by the AMP Master
blueprint (no manual steps needed).
In LDAP, we define the following permissions:
tenant
group:entityTagRegexesForNavigating
:acme.tenant.master
,acme.tenant.entity:${user}
(where${user}
is literal, the substitution done by the permissions engine)entityTagRegexesForReading
:acme.tenant.entity:${user}
entityTagRegexesForWriting
:acme.tenant.entity:${user}
admin
group:entityTagRegexesFor...
:.*
(all permissions)deployAllowed
serverInfoAllowed
root
(with this permission the others are redundant)
controller
group (WIP):entityTagRegexesFor...
:acme.tenant.master
,acme.tenant.entity:.*
(controllers given all rights to access master root node and all tenant entities at master, but not webapps)
Then in AMP, when a tenant logs in to the master, it will enforce:
- authentication: we look up user/password in ldap
- entitlements on the tenant AMP:
- we fetch the
acmePermission
attributes attached to the user and for those groups of which he/she is a member - we evaluate those permissions to determine whether they are allowed to see the tenant using the
tags
associated to the tenant entity. - (we store these permissions in a cache which is invalidated after 15m or whatever refresh time we require)
- we fetch the
A tenant can manage everything tagged with acme.tenant.entity:${USER}
Entitlements check on login at Tenant AMP instances, based on identity of which AMP server
This is deferred to the second iteration. Two options are:
- Updating LDAP on Tenant AMP creation and installing this at the tenant AMP
- A signed secret that the master AMP returns to the user to login to the tenant AMP that she claims to own
In the first iteration access to Tenant AMPs is driven by credentials stored in the Master AMP.
Features
- Users, credentials, and permissions are stored in one well-known external system
- Framework for defining entitlements which is already powerful, and easily extensible
Backlog
The entitlements for the entity/sensors/effectors on the tenant AMP will be addressed on the second iteration.
LDAP command reference
Currently, we added to the ldap schema a acmePermission
objectClass with 6 attributes:
entityTagRegexesForNavigating
entityTagRegexesForReading
entityTagRegexesForWriting
deployAllowed
serverInfoAllowed
root
Starting from a new openldap 2.4 instance, it is possible to add the acmePermission
objectClass by issuing the following commands:
ldapadd -Q -Y EXTERNAL -H ldapi:/// -f acme.ldif
Details
This ldif file has been created starting from the following acme.schema
placed in (/etc/ldap/schema/acme.schema)
attributetype (1.3.6.1.4.1.42.2.27.4.1.30
NAME 'root'
DESC 'regex to match entity tag that allows browsing entities'
EQUALITY booleanMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 )
attributetype (1.3.6.1.4.1.42.2.27.4.1.31
NAME 'deployAllowed'
DESC 'deployAllowed'
EQUALITY booleanMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 )
attributetype (1.3.6.1.4.1.42.2.27.4.1.32
NAME 'serverInfoAllowed'
DESC 'serverInfoAllowed'
EQUALITY booleanMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 )
attributetype ( 1.3.6.1.4.1.42.2.27.4.1.20
NAME 'entityTagRegexesForNavigating'
DESC 'regex to match entity tag that allows browsing entities'
EQUALITY caseExactMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
attributetype ( 1.3.6.1.4.1.42.2.27.4.1.21
NAME 'entityTagRegexesForReading'
DESC 'regex to match entity tag that allows reading entities'
EQUALITY caseExactMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
attributetype ( 1.3.6.1.4.1.42.2.27.4.1.22
NAME 'entityTagRegexesForWriting'
DESC 'regex to match entity tag that allows writing entities'
EQUALITY caseExactMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
objectclass ( 1.1.2.2.1 NAME 'acmePermission'
DESC 'permissions for Acme'
SUP top
AUXILIARY
MAY ( root $ deployAllowed $ serverInfoAllowed $ entityTagRegexesForNavigating $
entityTagRegexesForReading $ entityTagRegexesForWriting ) )
by using slaptest
utility.
cd /tmp/ldap
cat > schema_convert.conf <<EOT
include /etc/ldap/schema/core.schema
include /etc/ldap/schema/collective.schema
include /etc/ldap/schema/corba.schema
include /etc/ldap/schema/cosine.schema
include /etc/ldap/schema/duaconf.schema
include /etc/ldap/schema/dyngroup.schema
include /etc/ldap/schema/inetorgperson.schema
include /etc/ldap/schema/java.schema
include /etc/ldap/schema/misc.schema
include /etc/ldap/schema/nis.schema
include /etc/ldap/schema/openldap.schema
include /etc/ldap/schema/ppolicy.schema
include /etc/ldap/schema/ldapns.schema
include /etc/ldap/schema/pmi.schema
include /etc/ldap/schema/acme.schema
EOT
mkdir ldif_output
slapd -f schema_convert.conf -F .
slapcat -f schema_convert.conf -F ldif_output -n 0 | grep acme,cn=schema
# get the output
slapcat -f schema_convert.conf -F ldif_output -n0 -H \
ldap:///<output> -l cn=acme.ldif
# Edit cn=acme.ldif to arrive at the following attributes:
dn: cn=acme,cn=schema,cn=config
...
cn: acme
Also remove the following lines from the bottom:
structuralObjectClass: olcSchemaConfig
entryUUID: 52109a02-66ab-1030-8be2-bbf166230478
creatorsName: cn=config
createTimestamp: 20110829165435Z
entryCSN: 20110829165435.935248Z#000000#000#000000
modifiersName: cn=config
modifyTimestamp: 20110829165435Z
and finally
sudo ldapadd -Q -Y EXTERNAL -H ldapi:/// -f cn\=brooklyn.ldif