[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]

Direction on authentication, groups, and authorization in OpenShift

This is an attempt to summarize ongoing discussions around short/long term access control and other auth* topics in OpenShift.  The topic is NOT closed, and feedback on the specifics of intent are appreciated and welcome.  I've tried to include EVERYTHING that has been discussed in various threads, IRC chats, and prototypes.

Attempts have been made (where possible) to be consistent with OpenStack - https://wiki.openstack.org/wiki/KeystoneUseCases#Keystone_Use_Cases - but some terminology is already in use / has slightly different meanings for OpenShift.  The following concepts also take some direction from Amazon, although certain things like policies are too complex to consider in the short term.  I believe we can evolve access control in a manner that allows us to step from simple to complex gradually.

A prototype of team collaboration is available at https://github.com/smarterclayton/origin-server/tree/membership and is actively being executed on.


When a client program connects to the OpenShift REST API, or via SSH to a gear, there must be an *authentication* step that maps the client session to an underlying OpenShift user.    After authentication, OpenShift checks that the user is *authorized* to perform certain actions based on the resources they own or are granted access to.  Authorization can be restricted by *scoping* an authentication - an authorization token does this automatically - and only permissions (known as capabilities in other ACL systems) allowed by one of the active scopes can be performed.

Terms used below:

  resource   - an OpenShift entity such as Domain or Application
  user       - an OpenShift entity representing a person that logs into the service
               (in the future, there may be a subclass of a user called an organization)
  team       - a set of users that can be referenced as a single unit
  group      - an abstract set of users represented by an external system like LDAP or Kerberos principals
  member     - a user, team, or group that has been assigned a role on a resource
  permission - any action that may be taken in the system, 
               "view application", "edit alias", "create application" are all permissions
  role       - a set of permissions that may be taken on a given resource.
  capability - a flag or config value on a user that entitles the user to access certain features of OpenShift.

Within each section below are some short term and longer term objectives for changes that will allow team collaboration as simply as possible, and leave flexibility for future integration.



OpenShift has a pluggable Authentication Service that allows a 3rd party to handle the details of taking an incoming HTTP request (or username/password) and validating it against some backend.  This happens on a per request basis for every REST API request.  The service is responsible for handing OpenShift a user unique identifier (user uid) - a value that should not change over the lifetime of the user that will be used to find the user on subsequent authentications.  It's important to pick a user uid that does not change - for instance, email addresses in companies often change when people change their last names, which can mean that a user is no longer to access their old account.

Today, the authentication service cannot set default values on the user when they are created, but we would like to support it.  That would allow preinitialization of users based on information the authentication service has access to, such as LDAP groups.  Integrators may also update user capabilities directly based on data stored in Mongo or an external store like LDAP.

Authentication may also be done via an OAuth2 Bearer token (created as an authorization on a single OpenShift account).  When an API request is made using that token, the client is acting as the user who created the authorization, and has access to do everything that user does within the scopes allowed.  In the future, full OAuth2 support would be desirable for 3rd party integrators.

Application Gears (git and ssh)

On the gears, authentication is performed today via SSH public/private keys.  A user associates a public key with their user account, and then that key is propagated to all of the individual gears in the applications they have access to.  

In the future:

  * other key types may be associated with the user directly or indirectly (such as Kerberos principals)
  * those other types should be propagated to the gear
  * an integrator should be able to map a custom key type to an action on the node (via a node level plugin) so that other forms of authentication via SSH are possible.  
  * it should be possible for an integrator to disable public key authentication from the REST API, and have the clients respect that.
  * at user creation time or afterwards it should be possible for an integrator to add arbitrary key types to the user (and mark them undeletable or system managed)


Every API request is limited to the permissions the currently authenticated user is granted.  A user has full access to all resources they "own" - that means all domains that they created, and all applications created under that domain.  The "cost" of the resources is assessed to the owner, and the capabilities of the owner determine the remaining available actions on domains and applications.

In the short term, OpenShift will allow the owner of a domain to grant other users "membership" to their domain.  Membership on a domain is the explicit grant of a *role* to a user, where the role entitles that user to a set of permissions (simple RBAC model).  Roles are strictly inherited - a higher role has all of the permissions allowed by a lower role.  The following roles are suggested as a first pass:

  Role    Allows 
  ------- --------
  read    Viewing the domain
          Viewing the applications within that domain
          Viewing the other members of the domain and their role (limited personal information)
  control Everything allowed by "read"
          Starting and stopping applications and cartridges
          Scale cartridges
          See threaddumps
          Change quota
  edit    Everything allowed by "control"
          Add and remove cartridges
          Add and remove aliases
          Remote read/write SSH access to the gears and the Git repository
          Create and delete applications
  manage  Everything allowed by "edit"
          Change the namespace of a domain
          Add more members to a domain
          Limit the gear sizes allowed to applications within a domain
          Limit the number of gears created within a domain
          Delete a domain

  Note: these roles are simply examples based on existing use cases and more feedback is desired.  SSH access in the future might be split into a separate flag/role (gear role).

The owner cannot be removed from a domain, but managers are allowed to add members with manage access.  A user will be able to view the domains and applications they are members of.  Their keys will only be propagated to applications in domains where they have the "edit" role or higher.

Because domains are containers for applications, it is very valuable for an organizational user in multi-tenant environments to be able to limit what specific domains are entitled to do.  We envision this beginning with gear size limitations on the domain and a loose gear limit, which allows an owner to subdivide a large set of gears into domains that have specific uses and members (dev apps, staging, production).  The gear sizes available to create an application in a  domain are the gear sizes of the domain owner, intersected with the domain's allowed gear sizes.

Teams and Groups

Longer term, we would like to add the concept of Teams and/or Groups - a set of users (and potentially other groups) that can be added in bulk to a domain as a member with a specific role (defined when the team/group is added).  The difference is that teams are created and maintained within the application, whereas groups would map to an external resource like an LDAP group.  Integrators would be able to synchronize (best performance) or lookup at runtime (faster authorization) the underlying group information when constructing queries.  Roles would not be stored within LDAP.

When a team is added as a member, all of the users within that team would be added as "indirect" members.  A user who was already a member of the resource, and was granted indirect access via a team, and would have the higher of the two roles granted to them.  If their explicit access were removed, they would still have indirect access via their team at the team's role.  The denormalization of the team would allow very high performance access control queries (index lookups) which is an important characteristic of the system.  We expect a roughly long tail distribution of application membership, but even with very large fanout the mongoid index will be tractable.  Teams would be limited to a certain installation wide membership limit (100?) which would address the worst of the problem.  LDAP integration would either only materialize existing users of the system, or be opt in.

User Privacy

In highly multitenant environments access control needs to be cognizant of user privacy.  User A should be able to share a resource with user B without being able to catalog and list all of the users of OpenShift.  To that end, an invitation model would allow a user to send an email or copy a URL that will allow another user to accept the invitation (one time).  Users are implicitly able to view other users that they share membership on resources with - so subsequent grants require no invitation.  This is not a significant limitation in many enterprise deployments and so there should be a config flag to allow global invite.


Every REST API request to OpenShift occurs within one or more *scopes*.  By default, when a user authenticates directly via BASIC auth, Kerberos, or some other direct authentication, they will have the "session" scope which allows any action under their account.  However, the user may also create an authorization token which is granted a specific set of scopes, and when using that authorization token to access the API only actions allowed by those scopes can be performed (this includes viewing items as well as updating items).  A token with no scopes would allow no actions, and a token with multiple scopes allows the union of the permissions each scope grants.  Scopes do not allow you to perform any operation you cannot already perform, they merely limit the actions an API action could take.  Scopes allow another client to act on your behalf, but with a limited set of permissions.

The following scopes are either implemented or planned:

  userinfo             - retrieve basic info about the user
  read                 - view all the resources the user owns (except for authorization tokens), but no changes or updates are permitted
  application/:id/read   - read access to a single application, and read access to the domain that contains the application (but not other applications within the domain)
  application/:id/scale  - same as above, but with the ability to execute scale up / scale down events and set the scale multiplier
  application/:id/manage - grants access to perform all operations on a single app, and to have read access on the domain that contains the application
  domain/:id/read      - read access to a domain, and all of the applications within that domain
  domain/:id/edit      - all permissions on the domain and its applications that a member with the edit role on the domain could perform
  domain/:id/manage    - all permissions on the domain and its applications that a member with the manage role on the domain could perform
  session              - any operation can be performed as the user

Each scope has a timeout - we envision more limited scopes having much longer timeouts.

Scopes are implemented with three mechanisms:

  1. Reject access to an incoming web request by method and controller (read, userinfo)
  2. Limit the scope of queries made against the database by type (application/domain scopes)
  3. Authorize individual permissions against a known resource

By default, all code is organized so that permission is denied unless an implementer takes a specific action to enable a permission.  

Changes to REST API

This is very early, but we would want to change a few core resources to map to "the things I have access to" including:

   LIST_DOMAINS      /domains      - the domains I have access to
   LIST_APPLICATIONS /applications - the applications I have access to

We would introduce a new relation

   LIST_DOMAINS_BY_OWNER /domains  - a new required parameter "owner" would be the user identifier or login that owns the domain

Older API versions would return:

   LIST_DOMAINS /domains?owner= self

so that old clients don't become confused.  To bridge the gap with old clients, the sort order for the returned domains should display the domains sorted first by whether it is owned by the current user, and then by creation date.  This ensures that old clients continue to see the owner's first domain at the top of the list until they are ready to switch.

We want to expose membership, roles, and ownership on each resource that can have membership (domains and applications).  For applications, the membership list at first will simply be a mirror of the domain and be unalterable.  It should be easy to see who else has access to your resource, to edit that list, and to find members.  A provisional REST API list would be a sub resource of both Applications and Domains as:

   members: [
       'id' => <unique id of the member>,
       'type' => <type of the member> [user|group|team],
       'role' => <the role the member has> [read|control|edit|manage],
       'owner' => <true if this member owns this resource>,
       'from' => <an array of the different ways this user was granted access>,
       ... TBD

Membership changes would be POST/PUT/DELETE operations on a sub resource of both domain and application, and also able to be set during creation as well.  Other things, like invitations, team changes, and user searches need some more thought still.

When a user does not have view access to a particular resource, the response will always be 404 (to hide the existence of the object).  When a request is denied for authorization, role, or scope, the HTTP 403 response would be returned along with a message about why the request was rejected.  We will likely need to distinguish between various type of role failures for clients to be able to display appropriate messages.

[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]