“I’ll gladly pay you Tuesday for a Hamburger Today” –Wimpy, from the Popeye Cartoon.
Sometimes you need to authorize a service to perform an action on your behalf. Often, that action takes place long after any authentication token you can provide would have expired. Currently, the only mechanism in Keystone that people can use is to share credentials. We can do better.
During a discussion with people working on Heat we discussed the need to perform operations on a virtual machine on behalf of a user. For the High Availability use case, these types of actions might be:
- Live migrate a virtual machine from one host to another
- Stop a malfunctioning virtual machine
- Start up a spare virtual machine
Other use cases came up in another conversation. The common aspect is that they all require a service performing an action for a user at some point in the future, and the resources involved need to be linked to that user.
There are three mechanisms that come into play here: User Identity, User Role Assignment, and Policy Enforcement.
In the case of a service like HEAT, it is easiest to discuss ownership if we assume that there is a user account associated with HEAT. I’ll call this the “service user” although, in implementation, it may end up that each service has multiple users, perhaps one per endpoint.
Let us assume for a moment that we let the service perform the action as itself as opposed to assuming the identity of the user. Now any actions, such as “create Virtual Machine” will create a new resource owned by the service user.
If we make a mechanism “do on behalf of” we have two problems. First, we need to change each and every service to track who performed the action. Second we still need a mechanism to determine if the service user is authorized to perform the action. It is simpler to perform the action as the actual user.
User tokens are short lived. The time-out aspect is to limit the windows of time during which they are vulnerable to attack. Thus, the user cannot create a token now for an action that will be performed at an undermined time in the future. Any tokens issued now would have likely timed out.
Even if we were to allow tokens that do not time out, we would want to limit the actions that those tokens could be used to perform. This is where user role assignment becomes important. A user may have many roles inside a tenant, but only those roles exposed in the token will be used to authorize actions. If we were to create a role such as power_virtual_machine that allows only the ability to power off and power on a virtual machine, we could minimize a token used for High Availability to have only that role.
This is when the Policy Enforcement Mechanism becomes essential. Roles do not specify authorization. Instead, it is the the combination of assigned role and designated policy manage access to actions. Policy as designed today might be too coarse grained to mitigate actions in such a limited way. But the mechanisms are in place. They would merely require additional policy rules to enforce.
When a user sets up a service like High Availability, he creates a contract with the remote service. Part of that contract is that he will provide access to the resources necessary to carry out. It is this contract we want to record. Thus, we will provide the user the ability to create a pre-authentication record, or preauth for short. Once a user creates a preauth, the other user will be be able to use it to create a token to impersonate the first user.
When creating a preauth, a user will provide:
- authorized_user_id: The only user that will be able to call on the preauth
- roles: a subset of the roles to allow the remote token. This is expected to be an extremely limited set, designed to only allow the exact actions required by the impersonating user.
- expires: An optional date/time value that indicates when the preauth is no longer valid.
- endpoints: a set of endpoints for which the token is valid. If this list is empty or not defined, the token is valid on all endpoints.
- tenants: a set of endpoints for which the token is valid. If this list is empty or not defined, the token is not valid anywhere.
This will create a new resource, not a token. The resource will be identified by a UUID. The impersonating user will then be able to pass that UUID to the POST operation of the tokens URL in order to create a new token. The created token will be an ordinary token, with ordinary time-out limitations. It will, however, have an addition field alongside the list of roles: requested_by. This will be the UUID of the user that requested the token. This field will be ignored by auth_token middleware to start, but can be used in the future to maintain the audit trail for operations performed by the token.
Please feel free to ask me questions about it or provide feedback.