BMCWeb auth primer
Joseph Reynolds
jrey at linux.ibm.com
Tue Nov 12 12:41:02 AEDT 2019
Dear OpenBMC community and BMCWeb maintainers,
I worked on BMCWeb, learned how it works, and put together this little
primer on its authentication and authorization flows. I think portions
of it are generally useful to the community, and specifically useful to
help guide BMC security work. Certainly, I may want to refer back to it.
The material is more-or-less in markdown format, but no promises.
Take a peek, learn about auth security topics, correct my errors and
omissions, and let me know your ideas how to incorporate this into the
project. Thank you!
- Joseph
# BMCWeb auth primer
This describes the BMCWeb server's authentication and authorization
flows, showing how they relate to the usage shown in the [REDFISH
cheatsheet][] and [REST cheatsheet][], to [Phosphor User Manager][], and
to the [BMCWeb code][].
[REST cheatsheet]:
https://github.com/openbmc/docs/blob/master/REST-cheatsheet.md
[Redfish cheatsheet]:
https://github.com/openbmc/docs/blob/master/REDFISH-cheatsheet.md
[Phosphor User Manager]:
https://github.com/openbmc/docs/blob/master/architecture/user_management.md
[BMCWeb code]: https://github.com/openbmc/bmcweb
This first introduces UserSession objects, then described authentication
and all of its related features and code flows, and then covers
authorization flows.
## The SessionStore and UserSession objects
The SessionStore (a collection of UserSession objects) is a central
feature in BMCWeb's authentication and authorization flows.
Authenticated sessions are added to the session store and used to
authenticate subsequent accesses. The UserSession objects are then used
to determine which operations are allowed.
A UserSession object has fields including:
- username - the username of the account associated with this session.
- sessionToken - the secret access token to authenticate to this
session. This token is given to clients during login and passed back to
BMCWeb to authenticate access the session.
- csrfToken - the secret token to prevent XSRF attacks; this is needed
when SESSION cookies are used.
- the sessionId - the ID for this session, such as available via
/redfish/v1/SessionService/Sessions/${sessionId}.
The SessionStore and UserSession classes are in include/sessions.hpp.
## Authentication
Authentication establishes identity. BMCWeb creates or locates an
existing UserSession object and associates it with each HTTP request.
For background information about authentication in BMCWeb, refer to the
"Redfish Spec > Security details > Authentication"
https://www.dmtf.org/sites/default/files/standards/documents/DSP0266_1.8.0.pdf
In BMCWeb, you can authenticate to either:
- create a session (such as via login)
- use a session (via the sessionToken returned from a previous login), or
- perform a single operation (such as via Basic Access Authentication).
Most URIs require authentication for access, with exceptions for
whitelisted routes such as /redfish and /login. UserSession objects
created by login are added to the SessionStore and removed when either
the operation completes, logout, or timeout, as described below.
Temporary UserSession objects are created for login-less operations,
such as via Basic Auth.
BMCWeb supports several usage patterns for session-less access and for
logging in, using a session, and signing out. The following sections
give a simplified overview of the authentication credentials involved in
each operation. Specifically, every kind of authentication is shown
along with a stylized list of the credentials provided in the request,
and credentials returned in the response. The credentials are:
- ${u} is the username
- ${p} is the password
- ${base64_username_and_password} is the username and password base64
encoded
- ${S} is the session's sessionToken -- this secret is known only to
the client who created the session
- ${XSRF} is the session's csrfToken
- ${uniqueId} is the session's uniqueId -- this is not a secret, but
should not be predictable
The overview is simplified as follows: authentication is always
successful, JSON syntax is simplified, some JSON request and response
fields are omitted, and HTTP header syntax is simplified.
### Operations involving authorization
This shows all of BMCWeb's operations related to authorization.
#### Login APIs
Login creates a session and returns a sessionToken. The client can then
use the sessionToken for subsequent access to that session.
BMCWeb has two login APIs. The general flow for login APIs is: take a
username and password, validate them via Linux PAM, create a
UserSession, and return credentials needed to access that session. The
login APIs do not require authentication or authorization to invoke,
which means anyone on the BMC's network is allowed to access the API;
the login handler itself (as part of its function) authenticates the
user, and if successful, creates a session and returns credentials for
that session.
POST /login
This takes credentials in the HTTP request, either in the header or in
the body. The supported forms are shown below. Similarly this returns
credentials to the created session in various forms in the HTTP
response. The forms are:
- request body {username:${u}, password:${p}}
-> response body {token: ${S}}
- request body {data: {username:${u}, password:${p}}}
-> response body {token: ${S}}
- request headers "username: ${u}" and "password: ${p}"
-> response body {token: ${S}}
- request body {data: [${u},${p}]}
-> response header Set-Cookie: SESSION=${S} and XSRF-TOKEN=${XSRF}
This form corresponds to the REST cheatsheet /login example using
session cookies.
POST /redfish/v1/SessionService/Sessions/
- request body {username: ${u}, password:${p}}
-> response header contains: X-Auth-Token=${S} and response body
contains: {Id: ${uniqueId}}
#### Using a session
Creating a login session returns a sessionToken to the requesting
client. To use that session to perform an operation, token ${S} must be
provided in the request header. This token is used to locate the
UserSession in BMCWeb's SessionStore. The sessionToken can be provided
in one of these forms:
- request header X-Auth-Token: ${S}
- request header Authorization: Token ${S}
- request headers Cookie: SESSION=${S} and Cookie: XSRF-TOKEN=${XSRF}
-> No credentials are returned in the response.
Notes:
- Mix and match: No matter how a session is created, it can be used by
any of the supported mechanisms described here. The client may need to
copy session credentials from their login response into subsequent
requests. For example, you can create a session with the /login API,
read the sessionToken from the SESSION cookie, and supply it as the
X-Auth-Token on subsequent requests.
- BMCWeb has Cross Site Request Forgery (XSRF) protection for its
SESSION cookies. The only way to get the required XSRF-Token is to use
the login form that returns cookies.
- Although all of BMCWeb sessions appear as Session objects (under
/redfish/v1/SessionService/Sessions) and can be manipulated there, only
the Redfish login returns the session's uniqueId to the session's client.
#### Basic auth
Basic access authorization lets a client perform an operation without
having to first login. The username and password are provided as http
request headers and checked via PAM. BMCWeb creates a temporary session
and does not return any session credentials.
- request header Authorization: Basic $(base64_username_and_password}
-> No credentials are returned in the response.
#### Ending a session and timeout
Sessions can be explicitly terminated (aka logout) or can timeout.
The timeout duration and mechanism are in include/sessions.hpp /
SessionStore members timeoutInMinutes, applySessionTimeouts, etc.
The POST /logout operation terminates the current session; its handler
is in include/token_authorization_middleware.hpp / requestRoutes.
The DELETE /redfish/v1/SessionService/Sessions/${uniqueId} operation
deletes the specified session; its handler is in
redfish-core/lib/redfish_sessions.hpp / SessionCollection.doDelete().
#### Changing user passwords
A user account password can be changed via PATCH to
/redfish/v1/AccountService/Accounts/{useraccount} with JSON data
{Password: NEWPASSWORD}. The legacy API
/xyz/openbmc_project/user/root/action/SetPassword is no longer used.
### Restarting BMCWeb
BMCWeb preserves its SessionStore between restarts. This means that
when BMCWeb restarts, current sessions may experience a delay in
service, but will not have to login again and can use their existing
session. Specifically, BMCWeb writes its SessionStore to a file when
it exits, and reads it back in when it starts. The mechanism is
provided by include/sessions.hpp / SessionStore and
include/persistent_data_middleware.hpp / Middleware.
### Expired password - PENDING
The Redfish PasswordChangeRequired handling implementation is pending.
The approved design is here
https://github.com/openbmc/docs/blob/master/designs/expired-password.md
Pending implementation:
https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/25146
This design allows a user to login when their password is expired. When
authentication is successful but the account has an expired password,
the session is created with PasswordChangeRequired handling which means
it will have limited authority: it can change the password and log off.
### mTLS certificate-based authentication - PENDING
A mTLS certificate-based authentication implementation is pending.
The approved design is here:
https://github.com/openbmc/docs/blob/master/designs/redfish-tls-user-authentication.md
Pending implementation:
https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/23588
mTLS certificate-based authentication has the admin upload a user's
certificate to the BMC, then whenever that user contacts BMCWeb, the TLS
connection validates the certificate, thus both establishing the user's
identify and authenticating the user. BMCWeb uses a temporary session
for the operation.
- No credentials are provided in the request.
-> No credentials are returned in the response.
The mTLS certificate-based authentication has priority over other
authentication methods. Specifically, if TLS auth is successful where
there is an expired, password, the session will NOT be restricted by the
PasswordChangeRequired handling.
### Implementation
HTTP requests flow into token_authorization_middleware.hpp /
beforeHandle() which detects the authentication technique used and tries
to attach a UserSession object to the request. This function defines
the order in which authentication methods are attempted. Note that the
login URIs are on the whitelist, so their handlers are called
unauthenticated (with no UserSession object).
The POST /login operation handler is in
include/token_authorization_middleware.hpp / requestRoutes.
The POST /redfish/v1/SessionService/Sessions/ operation handler is in
redfish-core/lib/redfish_sessions.hpp / SessionCollection.doPost().
Authentication is performed via functions in
include/pam_authenticate.hpp such as pamAuthenticate.
## Authorization
Authorization determines if access to an operation is allowed. BMCWeb
first performs authentication then uses resulting UserSession object to
perform the authorization check. The user's Privilege Role is mapped to
a Redfish Role, then to Redfish Privileges which are compared against
the privilege required by the requested operation.
According to [OpenBMC User Management][], each user has a Privilege
Role. BMCWeb maps this to a Redfish Role, which then follows the
Redfish privilege model/Authorization subsystem which maps Users to
Roles, Roles to Privileges, and Operations to Privileges (where an
operation is an HTTP verb and URI). See the "Redfish Service
operation-to-privilege mapping" section of the spec. BMCWeb's
implementation is described in the next sections.
[OpenBMC User Management]:
https://github.com/openbmc/docs/blob/master/architecture/user_management.md
[Redfish Spec]:
https://www.dmtf.org/sites/default/files/standards/documents/DSP0266_1.8.0.pdf
### Basic flow
HTTP requests flow into http/routing.hpp Router.handle() (and
handleUpgrade()) which looks up two things:
- the user's Privilege Role (via sessions.hpp class UserRoleMap function
getUserRole), maps that to a Redfish Role, and maps that to the Redfish
Privileges. If there is no session (such as during a login request),
the request has no Privileges.
- the operation (the URI together with the verb or method); this
includes the Privileges required to perform that operation. Note that
the privileges required for Redfish operations use the Redfish
"Privilege AND and OR syntax".
The function checks the user's privileges against the privileges
required. Functions in redfish-core/include/privileges.hpp implement
the Redfish "Privilege AND and OR syntax".
The privilege checking in routing.hpp implements the Redfish
OperationMap, except:
- Does not implement property overrides.
- Does not implement subordinate overrides.
- Does not implement resource URI overrides.
- Does not properly implement the limitations of the ConfigureSelf
privilege which allows a user to operate only on their own account or
session. (See the proposal below.)
Note about unauthenticated requests. Certain whitelisted routes such as
"/redfish" and "/login" do not require authentication. There is no
UserSession object associated with these requests (the session field
contains the nullptr value even if authentication credentials were
provided). This request carries no privileges and is only allowed by
operations which do not require any privilege (such as the whitelisted
routes, including login).
PROPOSAL: This is a proposal to implement the Redfish ConfigureSelf
privilege, as implemented in the pending expired password design mention
above. The gist of the proposal is: Change the OperationMap for
operations such as PATCH ManagerAccount to be more permissive, allowing
any user who has ConfigureSelf privilege. This allows control to flow
into the handler where more information is available, where proper
authority checking can be performed.
Here's an example of how the ConfigureSelf privilege works. Per the
Redfish Privilege Registry, to PATCH a ManagerAccount requires the
ConfigureUsers privilege. Redfish also defines a Password Property
override: to PATCH the Password property of an account requires the
ConfigureSelf privilege. And the ConfigureSelf privilege itself applies
only to a user's own Account or Session. The overall effect is intended
to allow a user with the ConfigureUsers privilege to configure any user,
and all users to work with specific aspects of their Account.
BMCWeb's authorization mechanism handles the basic Redfish OperationMap,
but does not handle overrides or the ConfigureSelf privilege.
Continuing the example from the previous paragraph, BMCWeb allows any
user with ConfigureSelf privilege to access the handler to PATCH a
ManagerAccount. This is apparently violates of the Redfish spec. But
the handler itself then performs a more specific check, where it can
determine if the ConfigureSelf privilege should apply (only when the
session user is the same as the account's user) and can apply the
Password property override correctly.
The implementation re-uses parts of the authorization mechanism that was
performed in routing.hpp. Specifically, function
isAllowedWithoutConfigureSelf() defined in node.hpp uses the same
functions in privileges.hpp.
### User to Privilege Role mapping
The user's role is retrieved by function UserRoleMap.getUserRole in
sessions.hpp. This class uses D-Bus to dynamically get changes to User
Privilege Roles. That means each time a session is used, it uses the
then-current User role. (Note that versions before 10/2019 stored the
UserRole in the UserSession.)
### Flows to init the OperationMap
BMCWeb initializes all the URIs, verbs, and privilege required for each
when it starts running (in src/webserver_main.cpp / main()). The URI is
also referred to as an "entity" or a "route".
The Node class in redfish-core/include/node.hpp brings together a URI,
handlers for each verb, and an OperationMap. The OperationMap (as
Node.entityPrivileges) maps from an HTTP verb to the privileges
required. In the context of the Node, this defines the privileges
required for each operation.
The Node objects are all set up by the webserver_main.cpp, such as by
calls to various requestRoutes() functions and invocations of the
BMCWEB_ROUTE macro.
More information about the openbmc
mailing list