In my last post, I discussed how to extract the signing information out of a token. But just because the signature on a document is valid does not mean that the user who signed it was authorized to do so. How can we got from a signature to validating a token? Can we use that same mechanism to sign other OpenStack messages?
The following is a proposed extension to Keystone client based on existing mechanisms.
- Extract signer data out of the certificates
- Fetch the compete list of certificate from Keystone using the OS-SIMPLE-CERT extension
- Match the signer to the cert to validate the signature and extract the domain data for the token
- Fetch the mapping info from the Federation extension
- Use the mapping info to convert from the signing cert to a keystone user and groups
- Fetch the effective roles from Keystone for the user/groups for that domain
- Fetch policy from Keystone
- Execute the policy check to validate that the signer could sign for the data.
We need a method to go from the certificate used to sign the document to a valid Keystone user. Implied in there is that everything signed in an OpenStack system is going to be signed by a Keystone user. This is an expansion on how things were done in the past, but there is a pretty solid basis for this approach: in Kerberos, everything is a Principal, whether user or system.
From Tokens to Certs
The Token has the CMS Signer Info. We can extract that information as previously shown.
This might not scale greatly, it is sufficient for supporting a proof-of-concept. It reduces the problem of “how to find the cert for this token” down to a match between the signing info and the attributes of the certificates.
To extract the data from the certificates, We can Popen the OpenSSL command to validate a certificate. This is proper security practice anyway, as, while we trust the authoritative Keystone, we should verify whenever possible. It will be expensive, but this result can be cached and reused, so it should not have to happen very often.
From Certs to Users
To translate from a certificate to a user, we need to first parse the data out of the certificate. This is possible doing a call to OpenSSL. We can be especially efficient by using that call to validate the certificate itself, and then converting the response to a dictionary. Keystone already has a tool to convert a dictionary to the Identity objects (user and groups): the mapping mechanism in the Federation backend. Since a mapping is in a file we can fetch, we do not need to be inside the Keystone server to process the mapping, we just need to use the same mechanism.
And another to get each mapping.
Again, this will be expensive, but it can be cached now, and optimized in the future.
The same process that uses the mappings to translate the env-vars for an X509 certificate to a user inside the Keystone server can be performed externally. This means extracting code from the Federation plugin of the Keystone server to python-keystoneclient.
From User to Roles
Once we have the users and groups, we need to get the Role data appropriate to the token. This means validating the token, and extracting out the domain for the project. Then we will use the Identity API to list effective role assignments
We’ll probably have to call this once for the user ID and then once for each of the groups from the mapping in order to get the full set of roles.
From Roles to Permission
Now, how do we determine if the user was capable of signing for the specified token? We need a policy file. Which one? The one abstraction we currently have is that a policy file can be associated with an endpoint. Since keystone is responsible for controlling the signing of tokens, the logical endpoint is the authoritative keystone server where we are getting the certificates etc:
And now we can run the users RBAC through the policy engine to see if they can sign for the given token. The policy engine is part of oslo common. There is some “flattening” code from the Keystone server we will want to pull over. But of these will again land in python-keystoneclient.
This is a lot of communication with Keystone, but it should not have to be done very often: once each of these API calls have been made, the response can be cached for a reasonable amount of time. For example, a caching rule could say that all data is current for a minimum of 5 minutes. After that time, if a newly submitted token has an unknown signer info, the client could refetch the certificates. The notification mechanism from Keystone could also be extended to invalidate the cache of remote clients that register for such notifications.
For validating tokens in remote endpoints, the process will be split between python-keystoneclient and keystonemiddleware. The Middleware piece will be responsible for cache management and maintaining the state of the validation between calls. Keystone Client will expose each of the steps with a parameter that allows the cached state to be passed in, as well as already exposing the remote API wrapping functions.
At this state, it looks like no changes will have to be made to the Keystone server itself. This shows the power of the existing abstractions. In the future, some of the calls may need optimization. Of example, the fetch for certificates may need to be broken down into a call that fetches an individual certificate by its signing info.