21 KiB
Middleware Architecture
Abstract
The Keystone middleware architecture supports multiple authentication protocols in a pluggable manner in OpenStack. By providing support for authentication via pluggable authentication components, this architecture allows OpenStack services to be integrated easily into existing deployment environments. It also provides a path by which to implement support for emerging authentication standards such as OAUTH.
Rationale and Goals
Keystone is the Identity service for OpenStack. To support the easy integrating of OpenStack with existing authentication and identity management systems, Keystone supports talking to multiple backends like LDAP. And to support different deployment needs, it can support multiple authentication protocols via pluggable 'authentication components' implemented as WSGI middleware.
In this document, we describe the responsibilities of the authentication middleware. We describe how these interact with underlying OpenStack services and how existing services can be modified to take advantage of pluggable authentication. The goal is to allow OpenStack services to be integrated easily into existing deployment environments and to provide a path by which to implement support for emerging authentication standards such as OAUTH.
Specification Overview
'Authentication' is the process of determining that users are who they say they are. Typically, 'authentication protocols' such as HTTP Basic Auth, Digest Access, public key, token, etc, are used to verify a user's identity. In this document, we define an ''authentication component'' as a software module that implements an authentication protocol for an OpenStack service.
At a high level, an authentication component is simply a reverse
proxy that intercepts HTTP calls from clients. Once it has verified a
user's identity, the authentication component extends the call with
information about the current user and forwards the request to the
OpenStack service. Otherwise, if a user's identity is not verified, the
message is rejected before it gets to the service. This is illustrated
in authComponent
.
Authentication Component
Figure 1. Authentication Component
Authentication components may operate in 'delegated mode'. In this
mode, the decision reject an unauthenticated client is delegated to the
OpenStack service. Delegated mode is illustrated in authComponentDelegated
.
Here, requests are forwarded to the OpenStack service with an identity status message that indicates whether the client's identity has been confirmed or is indeterminate. It is the OpenStack service that decides whether or not a reject message should be sent to the client. Note that it is always the responsibility of the Authentication Component to transmit reject messages to the client.
Authentication Component (Delegated Mode)
Figure 2. Authentication Component (Delegated Mode)
In this architecture, we define interactions between the authentication component and the OpenStack service. Interactions between the client and the authentication component are defined only for exceptional cases. For example, we define the message that should be returned when the OpenStack service is down. Other interactions, however, are defined by the underlying authentication protocol and the OpenStack service and are considered out of scope.
Deployment Strategies
An authentication component may be integrated directly into the
service implementation, or it may be deployed separately as an HTTP
reverse proxy. This is illustrated in deployment
, showing both approaches to authentication,
labeled Option (a) and Option (b).
Authentication Component Deployments Options
Figure 3. Authentication Component Deployments Options
In Option (a), the component is integrated into the service implementation. In this case, communication between the authentication component and the service can be efficiently implemented via a method call. In Option (b), the component is deployed separately and communication between the service and the component involves an HTTP request. In both cases, unauthenticated requests are filtered before they reach the service.
Each approach offers some benefits. Option (a) offers low latency and ease of initial implementation, making it possibly most appropriate as a starting point for simple configurations. Option (b) offers several key advantages that may be of particular value in complex and dynamic configurations. It offers the ability to scale horizontally in cases where authentication is computationally expensive, such as when verifying digital signatures. Option (b) also allows authentication components to be written in different programming languages. Finally, Option (b) allows multiple authentication components to be deployed in front of the same service.
OpenStack services can support both embedded (Option (a)) and external (Option (b)) deployment strategies. Individual authentication components should support either strategy or they may may support both strategies. In order to support option (a), authentication components written in the Python programming language should be written as WSGI middleware components (in accordance with the Web Server Gateway Interface (WSGI) standard [PEP-333].
Additionally, services should support the ability to swap between different embedded or external authentication components via configuration options.
Exchanging User Information
If a request is successfully authenticated, the authentication
component must extend the request by adding an
X-Authorization
header. The header must must be formatted
as illustrated in xAuthHeader
.
X-Authorization Header
Example 1. X-Authorization Header:
X-Authorization: Proxy JoeUser
Here, Proxy denotes that the authentication occurred via a proxy (in this case authentication component) and ''JoeUser'' is the name of the user who issued the request.
We considered using an
Authorization
header rather than anX-Authorization
, thereby following normal HTTP semantics. There are some cases, however, where multipleAuthorization
headers need to be transmitted in a single request. We want to assure ourselves that this will not break common clients before we recommend the approach.
Authentication components may may extend the request with additional information. For example, an authentication system may add additional headers or modify the target URI to pass authentication information to the back-end service. Additionally, an authentication component may may strip sensitive information — a plain text password, for example — from the request. That said, an authentication component should should pass the majority of the request unmodified.
Reverse Proxy Authentication
An OpenStack service should should verify that it is receiving requests from a trusted authentication component. This is particularly important in cases where the authentication component and the OpenStack service are deployed separately. In order to trust incoming requests, the OpenStack service should therefore authenticate the authentication component. To avoid confusion, we call this 'reverse proxy authentication', since in this case the authentication component is acting as an HTTP reverse proxy.
Any HTTP-based authentication scheme may be used for reverse proxy authentication; however, all OpenStack services and all authentication components must must support HTTP Basic Authentication as defined in [RFC-2617].
Whether or not reverse proxy authentication is required is strictly a deployment concern. For example, an operations team may opt to utilize firewall rules instead of an authentication protocol to verify the integrity of incoming request. Because of this, both OpenStack services and authentication components must must also allow for unauthenticated communication.
In cases where reverse proxy authentication is used, the
authorization component may receive an HTTP 401 authentication error or
an HTTP 403 authorization error. These errors indicate that the
component does not have access to the underlying OpenStack service. The
authentication component "must not" "must not" return these errors to
the client application. Instead, the component must must return a 500
internal error. This is illustrated in proxyAuth
and proxyAuthDelegated
below. The component should should
format the errors in a manner that does not break the service contract
defined by the OpenStack service. proxyAuthDelegated
illustrates proxy authorization in
delegated mode. Delegated mode is discussed in detail in the next
section.
Reverse Proxy Authentication
Figure 4. Reverse Proxy Authentication
Reverse Proxy Authentication (Delegated Mode)
Figure 5. Reverse Proxy Authentication (Delegated Mode)
Delegated Mode
In some cases, the decision to reject an unauthenticated request should be delegated to the OpenStack service. An unauthenticated request may be appropriate in cases when anonymous access is allowed. In order to support these cases, an authentication component may be placed in Delegated Mode. In this mode, the component forwards requests to the OpenStack service when the client's identity has been confirmed or is indeterminate — that is when credentials are missing. The authentication component directly rejects requests with invalid credentials. Authentication components must must extend the request by adding an X-Identity-Status header. The identity status header must must contain one of the following values:
Identity Status Values
- Confirmed
-
A confirmed value indicates that valid credentials were sent and identity has been confirmed. The service can trust that the request has been sent on behalf of the user specified in the X-Authorization header.
- Indeterminate
-
An indeterminate value indicates that no credentials were sent and identity has not been confirmed. In this case, the service will receive an X-Authorization header with no user entry as illustrated in
xauth-header-indeterminate
.
Indeterminate Identity Headers
Example 2. Indeterminate Identity Headers:
X-Identity-Status: Indeterminate
X-Authorization: Proxy
Services may may reject a delegated request by issuing an HTTP 401
authentication error or an HTTP 403 authorization error. These responses
must must contain an WWW-Authenticate
header with a value
of Delegated
as illustrated in unauthHeaders
.
- X-Identity-Status
-
Provides information on whether the request was authenticated or not.
- X-Tenant
-
Provides the tenant ID (as it appears in the URL in Keystone). This is to support any legacy implementations before Keystone switched to an ID/Name schema for tenants.
- X-Tenant-Id
-
The unique, immutable tenant Id
- X-Tenant-Name
-
The unique, but mutable (it can change) tenant name.
- X-User-Id
-
The user id of the user used to log in
- X-User-Name
-
The username used to log in
- X-User
-
The username used to log in. This is to support any legacy implementations before Keystone switched to an ID/Name schema for tenants.
- X-Roles
-
The roles associated with that user
Delegated WWW-Authenticate Header
WWW-Authenticate: Delegated
It is important to note that the actual reject message will likely be
modified by the authentication component in order to comply with the
authentication scheme it is implementing. This is illustrated in delegateRejectBasic
and delegateRejectOAuth
below.
Delegated Reject Basic Auth
Delegated Reject OAuth
The presence of the WWW-Authenticate
header with a value of Delegated
distinguishes a client authentication/authorization failure from a
component failure. For example, compare delegateForbidden
with proxyAuthDelegated
. In delegateForbidden
, the client is not allowed to access
the OpenStack service. In proxyAuthDelegated
, it is the authentication component
itself which is unauthorized.
Delegated Reject Forbidden
Figure 8. Delegated Reject Forbidden
Authentication components must must support both delegated and undelegated (standard) modes. Delegated mode should should be configured via a configuration option. Delegated mode should should be disabled by default.
OpenStack services are not required to support delegated mode. If a
service does not support delegated mode, it must must respond with a 501
not implemented error and an WWW-Authenticate header with a value of Delegated. The authentication component "must
not" "must not" return the error to the client application. Instead, the
component must must return a 500 internal error; this is illustrated in
delegateUnimplemented
.
The component should should format the error in a manner that does not
break the service contract defined by the OpenStack service. The
component should also log the error such that it that will inform
operators of the misconfiguration.
Unimplemented Delegated Mode
Handling Direct Client Connections
Requests from the authentication component to an OpenStack service
must must contain an X-Authorization
header. If the header
is missing, and reverse proxy authentication fails or is switched off,
the OpenStack service may may assume that the request is coming directly
from a client application. In this case, the OpenStack service must must
redirect the request to the authentication component by issuing an HTTP
305 User Proxy redirect. This is illustrated in redirect
. Note that the
redirect response must must include a Location
header
specifying the authentication component's URL as shown in redirect-response
.
Auth Component Redirect
Auth Component Redirect Response
HTTP/1.1 305 Use Proxy
Date: Thu, 28 Oct 2011 07:41:16 GMT
Location: http://sample.auth.openstack.com/path/to/resource
Using Multiple Authentication Components
There are some use cases when a service provider might want to
consider using multiple authentication components for different
purposes. For instance, a service provider may have one authentication
scheme to authenticate the users of the service and another one to
authenticate the administrators or operations personnel that maintain
the service. For such scenarios, we propose using a mapper as
illustrated in multiAuth
.
Multiple Authentication Components
At a high level, a mapper is a simple reverse proxy that intercepts HTTP calls from clients and routes the request to the appropriate authentication component. A mapper can make the routing decisions based on a number of routing rules that map a resource to a specific authentication component. For example, a request URI may determine whether a call should be authenticated via one authentication component or another.
Note that neither the authentication component nor the OpenStack service need be aware of the mapper. Any external authentication component can be used alongside others. Mappers may provide a means by which to offer support for anonymous or guest access to a subset of service resources. A mapper may be implemented via a traditional reverse proxy server such as Pound or Zeus.
The Default Component
Individual services must must be distributed with a simple integrated authentication component by default. Providing such a component lowers barriers to the deployment of individual services. This is especially important to] developers who may want to deploy OpenStack services on their own machines. Also, since there is no direct dependency on an external authentication system, OpenStack services can be deployed individually, without the need to stand up and configure additional services. Finally, having a standard authentication component that all services share promotes a separation of concerns. That is, as a community we are explicitly stating that services should not develop their own authentication mechanisms. Additional authentication components may be developed, of course, but these components should not be intimately coupled to any one particular service.
As discussed in deployStrategies
, an authentication component may be
integrated directly into the service implementation (Option (a)), or it
may be deployed separately as an HTTP reverse proxy (Option (b)). The
default component should be implemented to support Option (a) and
services should maintain support for Option (b). One way to achieve this
is to provide a method that allows the disabling of the default
authentication component via configuration. This is illustrated in both
. Here, requests are sent
directly to the OpenStack service when the default authentication
component is disabled.
We will discuss the design of the default component in an upcoming blueprint.
Disabled Embedded Component
Questions and Answers
Why do authentication components send reject messages? Why not have OpenStack services reject requests themselves?
The content and format of an authentication failed message is determined by the authentication scheme (or protocol). For the service to respond appropriately, it would have to be aware of the authentication scheme in which it participates; this defeats the purpose of pluggable authentication components.
Why require support for deploying authentication components in separate nodes?
The deployment strategy is very flexible. It allows for authentication components to be horizontally scalable. It allows for components to be written in different languages. Finally, it allows different authentication components to be deployed simultaneously as described above.
References
- PEP-333
-
pep0333 Phillip J Eby. 'Python Web Server Gateway Interface v1.0.'' http://www.python.org/dev/peps/pep-0333/.
- RFC-2617
-
rfc2617 J Franks. P Hallam-Baker. J Hostetler. S Lawrence. P Leach. A Luotonen. L Stewart. ''HTTP Authentication: Basic and Digest Access Authentication.'' http://tools.ietf.org/html/rfc2617.