521 lines
17 KiB
ReStructuredText
521 lines
17 KiB
ReStructuredText
..
|
|
Copyright 2011-2012 OpenStack Foundation
|
|
All Rights Reserved.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
not use this file except in compliance with the License. You may obtain
|
|
a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
License for the specific language governing permissions and limitations
|
|
under the License.
|
|
|
|
=====================
|
|
Keystone Architecture
|
|
=====================
|
|
|
|
Much of the design assumes that in most deployments auth backends will be shims
|
|
in front of existing user systems.
|
|
|
|
|
|
Services
|
|
========
|
|
|
|
Keystone is organized as a group of internal services exposed on one or many
|
|
endpoints. Many of these services are used in a combined fashion by the
|
|
frontend. For example, an authenticate call will validate user/project
|
|
credentials with the Identity service and, upon success, create and return a
|
|
token with the Token service.
|
|
|
|
|
|
Identity
|
|
--------
|
|
|
|
The Identity service provides auth credential validation and data about `users`
|
|
and `groups`. In the basic case, this data is managed by the Identity service,
|
|
allowing it to also handle all CRUD operations associated with this data. In
|
|
more complex cases, the data is instead managed by an authoritative backend
|
|
service. An example of this would be when the Identity service acts as a
|
|
frontend for LDAP. In that case the LDAP server is the source of truth and the
|
|
role of the Identity service is to relay that information accurately.
|
|
|
|
Users
|
|
^^^^^
|
|
|
|
``Users`` represent an individual API consumer. A user itself must be owned by
|
|
a specific domain, and hence all user names are **not** globally unique, but
|
|
only unique to their domain.
|
|
|
|
Groups
|
|
^^^^^^
|
|
|
|
``Groups`` are a container representing a collection of users. A group itself
|
|
must be owned by a specific domain, and hence all group names are **not**
|
|
globally unique, but only unique to their domain.
|
|
|
|
Resource
|
|
--------
|
|
|
|
The Resource service provides data about `projects` and `domains`.
|
|
|
|
Projects
|
|
^^^^^^^^
|
|
|
|
``Projects`` represent the base unit of ``ownership`` in OpenStack, in that all
|
|
resources in OpenStack should be owned by a specific project. A project itself
|
|
must be owned by a specific domain, and hence all project names are **not**
|
|
globally unique, but unique to their domain. If the domain for a project is not
|
|
specified, then it is added to the default domain.
|
|
|
|
Domains
|
|
^^^^^^^
|
|
|
|
``Domains`` are a high-level container for projects, users and groups. Each is
|
|
owned by exactly one domain. Each domain defines a namespace where an
|
|
API-visible name attribute exists. Keystone provides a default domain, aptly
|
|
named 'Default'.
|
|
|
|
In the Identity v3 API, the uniqueness of attributes is as follows:
|
|
|
|
- Domain Name. Globally unique across all domains.
|
|
|
|
- Role Name. Unique within the owning domain.
|
|
|
|
- User Name. Unique within the owning domain.
|
|
|
|
- Project Name. Unique within the owning domain.
|
|
|
|
- Group Name. Unique within the owning domain.
|
|
|
|
Due to their container architecture, domains may be used as a way to delegate
|
|
management of OpenStack resources. A user in a domain may still access
|
|
resources in another domain, if an appropriate assignment is granted.
|
|
|
|
|
|
Assignment
|
|
----------
|
|
|
|
The Assignment service provides data about `roles` and `role assignments`.
|
|
|
|
Roles
|
|
^^^^^
|
|
|
|
``Roles`` dictate the level of authorization the end user can obtain. Roles
|
|
can be granted at either the domain or project level. A role can be assigned at
|
|
the individual user or group level. Role names are unique within the
|
|
owning domain.
|
|
|
|
Role Assignments
|
|
^^^^^^^^^^^^^^^^
|
|
|
|
A 3-tuple that has a ``Role``, a ``Resource`` and an ``Identity``.
|
|
|
|
Token
|
|
-----
|
|
|
|
The Token service validates and manages tokens used for authenticating requests
|
|
once a user's credentials have already been verified.
|
|
|
|
|
|
Catalog
|
|
-------
|
|
|
|
The Catalog service provides an endpoint registry used for endpoint discovery.
|
|
|
|
|
|
Application Construction
|
|
========================
|
|
|
|
Keystone is an HTTP front-end to several services. Since the Rocky release Keystone
|
|
uses the `Flask-RESTful`_ library to provide a REST API interface to these services.
|
|
|
|
.. _`Flask-RESTful`: https://flask-restful.readthedocs.io/en/latest/
|
|
|
|
Keystone defines functions related to `Flask-RESTful`_ in
|
|
:mod:`keystone.server.flask.common`. Keystone creates API resources which
|
|
inherit from class :mod:`keystone.server.flask.common.ResourceBase` and exposes methods
|
|
for each supported HTTP methods GET, PUT , POST, PATCH and DELETE. For example, the User
|
|
resource will look like:
|
|
|
|
.. code-block:: python
|
|
|
|
class UserResource(ks_flask.ResourceBase):
|
|
collection_key = 'users'
|
|
member_key = 'user'
|
|
get_member_from_driver = PROVIDERS.deferred_provider_lookup(
|
|
api='identity_api', method='get_user')
|
|
|
|
def get(self, user_id=None):
|
|
"""Get a user resource or list users.
|
|
GET/HEAD /v3/users
|
|
GET/HEAD /v3/users/{user_id}
|
|
"""
|
|
...
|
|
|
|
def post(self):
|
|
"""Create a user.
|
|
POST /v3/users
|
|
"""
|
|
...
|
|
|
|
class UserChangePasswordResource(ks_flask.ResourceBase):
|
|
@ks_flask.unenforced_api
|
|
def post(self, user_id):
|
|
...
|
|
|
|
Routes for each API resource are defined by classes which inherit from
|
|
:mod:`keystone.server.flask.common.APIBase`. For example, the UserAPI will
|
|
look like:
|
|
|
|
.. code-block:: python
|
|
|
|
class UserAPI(ks_flask.APIBase):
|
|
_name = 'users'
|
|
_import_name = __name__
|
|
resources = [UserResource]
|
|
resource_mapping = [
|
|
ks_flask.construct_resource_map(
|
|
resource=UserChangePasswordResource,
|
|
url='/users/<string:user_id>/password',
|
|
resource_kwargs={},
|
|
rel='user_change_password',
|
|
path_vars={'user_id': json_home.Parameters.USER_ID}
|
|
),
|
|
...
|
|
|
|
The methods ``_add_resources()`` or ``_add_mapped_resources()`` in
|
|
:mod:`keystone.server.flask.common.APIBase` bind the resources with the APIs.
|
|
Within each API, one or more managers are loaded (for example, see
|
|
:mod:`keystone.catalog.core.Manager`), which are thin wrapper classes which load
|
|
the appropriate service driver based on the keystone configuration.
|
|
|
|
* Assignment
|
|
|
|
* :mod:`keystone.api.role_assignments`
|
|
* :mod:`keystone.api.role_inferences`
|
|
* :mod:`keystone.api.roles`
|
|
* :mod:`keystone.api.os_inherit`
|
|
* :mod:`keystone.api.system`
|
|
|
|
* Authentication
|
|
|
|
* :mod:`keystone.api.auth`
|
|
* :mod:`keystone.api.ec2tokens`
|
|
* :mod:`keystone.api.s3tokens`
|
|
|
|
* Catalog
|
|
|
|
* :mod:`keystone.api.endpoints`
|
|
* :mod:`keystone.api.os_ep_filter`
|
|
* :mod:`keystone.api.regions`
|
|
* :mod:`keystone.api.services`
|
|
|
|
* Credentials
|
|
|
|
* :mod:`keystone.api.credentials`
|
|
|
|
* Federation
|
|
|
|
* :mod:`keystone.api.os_federation`
|
|
|
|
* Identity
|
|
|
|
* :mod:`keystone.api.groups`
|
|
* :mod:`keystone.api.users`
|
|
|
|
* Limits
|
|
|
|
* :mod:`keystone.api.registered_limits`
|
|
* :mod:`keystone.api.limits`
|
|
|
|
* Oauth1
|
|
|
|
* :mod:`keystone.api.os_oauth1`
|
|
|
|
* Policy
|
|
|
|
* :mod:`keystone.api.policy`
|
|
|
|
* Resource
|
|
|
|
* :mod:`keystone.api.domains`
|
|
* :mod:`keystone.api.projects`
|
|
|
|
* Revoke
|
|
|
|
* :mod:`keystone.api.os_revoke`
|
|
|
|
* Trust
|
|
|
|
* :mod:`keystone.api.trusts`
|
|
|
|
Service Backends
|
|
================
|
|
|
|
Each of the services can be configured to use a backend to allow keystone to
|
|
fit a variety of environments and needs. The backend for each service is
|
|
defined in the keystone.conf file with the key ``driver`` under a group
|
|
associated with each service.
|
|
|
|
A general class exists under each backend to provide an abstract base class
|
|
for any implementations, identifying the expected service implementations. The
|
|
abstract base classes are stored in the service's backends directory as
|
|
``base.py``. The corresponding drivers for the services are:
|
|
|
|
* :mod:`keystone.assignment.backends.base.AssignmentDriverBase`
|
|
* :mod:`keystone.assignment.role_backends.base.RoleDriverBase`
|
|
* :mod:`keystone.auth.plugins.base.AuthMethodHandler`
|
|
* :mod:`keystone.catalog.backends.base.CatalogDriverBase`
|
|
* :mod:`keystone.credential.backends.base.CredentialDriverBase`
|
|
* :mod:`keystone.endpoint_policy.backends.base.EndpointPolicyDriverBase`
|
|
* :mod:`keystone.federation.backends.base.FederationDriverBase`
|
|
* :mod:`keystone.identity.backends.base.IdentityDriverBase`
|
|
* :mod:`keystone.identity.mapping_backends.base.MappingDriverBase`
|
|
* :mod:`keystone.identity.shadow_backends.base.ShadowUsersDriverBase`
|
|
* :mod:`keystone.oauth1.backends.base.Oauth1DriverBase`
|
|
* :mod:`keystone.policy.backends.base.PolicyDriverBase`
|
|
* :mod:`keystone.resource.backends.base.ResourceDriverBase`
|
|
* :mod:`keystone.resource.config_backends.base.DomainConfigDriverBase`
|
|
* :mod:`keystone.revoke.backends.base.RevokeDriverBase`
|
|
* :mod:`keystone.token.providers.base.Provider`
|
|
* :mod:`keystone.trust.backends.base.TrustDriverBase`
|
|
|
|
If you implement a backend driver for one of the keystone services, you're
|
|
expected to subclass from these classes.
|
|
|
|
|
|
Templated Backend
|
|
-----------------
|
|
|
|
Largely designed for a common use case around service catalogs in the keystone
|
|
project, a templated backend is a catalog backend that simply expands
|
|
pre-configured templates to provide catalog data.
|
|
|
|
Example paste.deploy config (uses $ instead of % to avoid ConfigParser's
|
|
interpolation)
|
|
|
|
.. code-block:: ini
|
|
|
|
[DEFAULT]
|
|
catalog.RegionOne.identity.publicURL = http://localhost:$(public_port)s/v3
|
|
catalog.RegionOne.identity.adminURL = http://localhost:$(public_port)s/v3
|
|
catalog.RegionOne.identity.internalURL = http://localhost:$(public_port)s/v3
|
|
catalog.RegionOne.identity.name = 'Identity Service'
|
|
|
|
|
|
Data Model
|
|
==========
|
|
|
|
Keystone was designed from the ground up to be amenable to multiple styles of
|
|
backends. As such, many of the methods and data types will happily accept more
|
|
data than they know what to do with and pass them on to a backend.
|
|
|
|
There are a few main data types:
|
|
|
|
* **User**: has account credentials, is associated with one or more projects or domains
|
|
* **Group**: a collection of users, is associated with one or more projects or domains
|
|
* **Project**: unit of ownership in OpenStack, contains one or more users
|
|
* **Domain**: unit of ownership in OpenStack, contains users, groups and projects
|
|
* **Role**: a first-class piece of metadata associated with many user-project pairs.
|
|
* **Token**: identifying credential associated with a user or user and project
|
|
* **Extras**: bucket of key-value metadata associated with a user-project pair.
|
|
* **Rule**: describes a set of requirements for performing an action.
|
|
|
|
While the general data model allows a many-to-many relationship between users
|
|
and groups to projects and domains; the actual backend implementations take
|
|
varying levels of advantage of that functionality.
|
|
|
|
|
|
Approach to CRUD
|
|
================
|
|
|
|
While it is expected that any "real" deployment at a large company will manage
|
|
their users and groups in their existing user systems, a variety of CRUD
|
|
operations are provided for the sake of development and testing.
|
|
|
|
CRUD is treated as an extension or additional feature to the core feature set,
|
|
in that a backend is not required to support it. It is expected that
|
|
backends for services that don't support the CRUD operations will raise a
|
|
:mod:`keystone.exception.NotImplemented`.
|
|
|
|
|
|
Approach to Authorization (Policy)
|
|
==================================
|
|
|
|
Various components in the system require that different actions are allowed
|
|
based on whether the user is authorized to perform that action.
|
|
|
|
For the purposes of keystone there are only a couple levels of authorization
|
|
being checked for:
|
|
|
|
* Require that the performing user is considered an admin.
|
|
* Require that the performing user matches the user being referenced.
|
|
|
|
Other systems wishing to use the policy engine will require additional styles
|
|
of checks and will possibly write completely custom backends. By default,
|
|
keystone leverages policy enforcement that is maintained in `oslo.policy
|
|
<https://opendev.org/openstack/oslo.policy/>`_.
|
|
|
|
|
|
Rules
|
|
-----
|
|
|
|
Given a list of matches to check for, simply verify that the credentials
|
|
contain the matches. For example:
|
|
|
|
.. code-block:: python
|
|
|
|
credentials = {'user_id': 'foo', 'is_admin': 1, 'roles': ['nova:netadmin']}
|
|
|
|
# An admin only call:
|
|
policy_api.enforce(('is_admin:1',), credentials)
|
|
|
|
# An admin or owner call:
|
|
policy_api.enforce(('is_admin:1', 'user_id:foo'), credentials)
|
|
|
|
# A netadmin call:
|
|
policy_api.enforce(('roles:nova:netadmin',), credentials)
|
|
|
|
Credentials are generally built from the user metadata in the 'extras' part
|
|
of the Identity API. So, adding a 'role' to the user just means adding the role
|
|
to the user metadata.
|
|
|
|
|
|
Capability RBAC
|
|
---------------
|
|
|
|
(Not yet implemented.)
|
|
|
|
Another approach to authorization can be action-based, with a mapping of roles
|
|
to which capabilities are allowed for that role. For example:
|
|
|
|
.. code-block:: python
|
|
|
|
credentials = {'user_id': 'foo', 'is_admin': 1, 'roles': ['nova:netadmin']}
|
|
|
|
# add a policy
|
|
policy_api.add_policy('action:nova:add_network', ('roles:nova:netadmin',))
|
|
|
|
policy_api.enforce(('action:nova:add_network',), credentials)
|
|
|
|
In the backend this would look up the policy for 'action:nova:add_network' and
|
|
then do what is effectively a 'Simple Match' style match against the credentials.
|
|
|
|
Approach to Authentication
|
|
==========================
|
|
|
|
Keystone provides several authentication plugins that inherit from
|
|
:mod:`keystone.auth.plugins.base`. The following is a list of available plugins.
|
|
|
|
* :mod:`keystone.auth.plugins.external.Base`
|
|
* :mod:`keystone.auth.plugins.mapped.Mapped`
|
|
* :mod:`keystone.auth.plugins.oauth1.OAuth`
|
|
* :mod:`keystone.auth.plugins.password.Password`
|
|
* :mod:`keystone.auth.plugins.token.Token`
|
|
* :mod:`keystone.auth.plugins.totp.TOTP`
|
|
|
|
In the most basic plugin ``password``, two pieces of information are required
|
|
to authenticate with keystone, a bit of ``Resource`` information and a bit of
|
|
``Identity``.
|
|
|
|
Take the following call POST data for instance:
|
|
|
|
.. code-block:: javascript
|
|
|
|
{
|
|
"auth": {
|
|
"identity": {
|
|
"methods": [
|
|
"password"
|
|
],
|
|
"password": {
|
|
"user": {
|
|
"id": "0ca8f6",
|
|
"password": "secretsecret"
|
|
}
|
|
}
|
|
},
|
|
"scope": {
|
|
"project": {
|
|
"id": "263fd9"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
The user (ID of 0ca8f6) is attempting to retrieve a token that is scoped to
|
|
project (ID of 263fd9).
|
|
|
|
To perform the same call with names instead of IDs, we now need to supply
|
|
information about the domain. This is because usernames are only unique within
|
|
a given domain, but user IDs are supposed to be unique across the deployment.
|
|
Thus, the auth request looks like the following:
|
|
|
|
.. code-block:: javascript
|
|
|
|
{
|
|
"auth": {
|
|
"identity": {
|
|
"methods": [
|
|
"password"
|
|
],
|
|
"password": {
|
|
"user": {
|
|
"domain": {
|
|
"name": "acme"
|
|
}
|
|
"name": "userA",
|
|
"password": "secretsecret"
|
|
}
|
|
}
|
|
},
|
|
"scope": {
|
|
"project": {
|
|
"domain": {
|
|
"id": "1789d1"
|
|
},
|
|
"name": "project-x"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
For both the user and the project portion, we must supply either a domain ID
|
|
or a domain name, in order to properly determine the correct user and project.
|
|
|
|
Alternatively, if we wanted to represent this as environment variables for a
|
|
command line, it would be:
|
|
|
|
.. code-block:: bash
|
|
|
|
$ export OS_PROJECT_DOMAIN_ID=1789d1
|
|
$ export OS_USER_DOMAIN_NAME=acme
|
|
$ export OS_USERNAME=userA
|
|
$ export OS_PASSWORD=secretsecret
|
|
$ export OS_PROJECT_NAME=project-x
|
|
|
|
Note that the project the user is attempting to access must be in the same
|
|
domain as the user.
|
|
|
|
What is Scope?
|
|
--------------
|
|
|
|
Scope is an overloaded term.
|
|
|
|
In reference to authenticating, as seen above, scope refers to the portion of
|
|
the POST data that dictates what ``Resource`` (project, domain, or system) the
|
|
user wants to access.
|
|
|
|
In reference to tokens, scope refers to the effectiveness of a token,
|
|
i.e.: a `project-scoped` token is only useful on the project it was initially
|
|
granted for. A `domain-scoped` token may be used to perform domain-related
|
|
function. A `system-scoped` token is only useful for interacting with APIs that
|
|
affect the entire deployment.
|
|
|
|
In reference to users, groups, and projects, scope often refers to the domain
|
|
that the entity is owned by. i.e.: a user in domain X is scoped to domain X.
|