Last active
December 15, 2015 07:49
-
-
Save termie/5225817 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Delegated Auth a la Oauth | |
========================= | |
It's pronounced "wawth," btw. | |
Problem Statement | |
----------------- | |
Third-Party Service A, we'll call it ScaleMe, needs to launch new servers with | |
Nova on behalf of User A, we'll call her Abby. | |
Abby doesn't want ScaleMe to be able to do _everything_ on the system, just | |
launch servers in a specific project, so she only wants it to have the role | |
that nova checks for to launch servers, let's call that role | |
`nova:server_launcher`. | |
Now, Keystone tokens have expirations, and Abby doesn't want to have to give | |
ScaleMe new tokens all the time, especially since ScaleMe is probably going to | |
be doing all this server launching while Abby is asleep because her service | |
has a large New Zealander, we'll call them Kiwis, userbase. | |
What we seem to need is a way for Abby to allow ScaleMe to get new tokens for | |
her that only have the `nova:server_launcher` role. | |
So Let's Just Do That Then | |
-------------------------- | |
A quick way to solve that in non-specific terms. Don't worry, we'll get way | |
more specific later, like so specific you won't even want to read it all. But | |
for now the bird's eye is the lens through which we will look. | |
0. Before this all starts, ScaleMe needs to be given some sort of credentials | |
that Keystone can use to make sure this is somebody it should bother | |
listening to (and can revoke access for). | |
1. Abby needs to provide ScaleMe with an additional set of credentials that | |
map to her, her project and the specific roles she wishes it to have. | |
2. ScaleMe needs to use both sets of credentials to get a standard Keystone | |
token whenever it needs to perform operations on behalf of Abby. | |
Easy as pie. And remember, when making pie crust that you want to put the butter in the freezer for a while before cubing it and rolling it into the dough so that you get better pockets of delicious flakeyness. This is like that. | |
Making the Pie Filling | |
---------------------- | |
I'm a Strawberry-Rhubarb fan, it's a pretty tried and true combination, but | |
some people find it rather tart at first taste. I kind of like that tartness, | |
but I'm also a little weird, probably. Your mileage may vary. I have a friend | |
who makes delicious strawberry rhubarb pies though, she's amazing. | |
Let's Do It With Oauth | |
---------------------- | |
Wawth to the rescue. Turns out the interactions described above are explicitly | |
in the scope of what Oauth is about. Let's repeat the steps above, but using | |
the Oauth terminology to be a little more specific. Still way more specificity | |
coming later though, promise. | |
0. Keystone creates a consumer key and secret and gives them to ScaleMe | |
out of band. | |
1. Abby, through an Oauth exchange, provides ScaleMe with an access token and | |
secret mapped to the specific roles she is giving ScaleMe. | |
2. ScaleMe makes an Oauth signed request to a specific API on Keystone that | |
will generate the appropriate Keystone token and provide it to ScaleMe. | |
But Oauth Is Scary And What Is It | |
--------------------------------- | |
Wawth had some bad PR and marketing breaks due to big enterprise deciding they | |
liked it so much that they were going to make a completely unrelated new spec | |
and call it Oauth2. We're not using that one, we're talking about Oauth 1.x. | |
You're probably already familiar with it if you use Twitter or other social | |
websites, it is what is happening when you authorize a website to use your | |
Twitter account. | |
Oauth is mostly two things. On the most minimal level it is a way to sign | |
form fields to prove that the request is valid and from who it says it is from. | |
We'll just call that Oauth Signing. The signing process takes a little reading | |
to understand but suffice it to say there are many, many, well-tested and | |
interoperable libraries that know how to do Oauth Signing. | |
In the bigger scope, Oauth is a pattern for authorizing third-parties to take | |
actions on behalf of a user via a two-phase token exchange. In plainer terms, | |
the user is going to get a request to authorize the third party and once they | |
authorize them the third party will have a token it can use for future API | |
calls. | |
Let's roll all this stuff together and do the big highly specific version. | |
Get Ready To Get Specific | |
------------------------- | |
Are you ready? Are you primed? ARE YOU PUMPED!? | |
Get Specific | |
------------ | |
At memory location 0x4AC01F... | |
Just kidding, let's get this show rolling, enough ado, no more waiting, the | |
introductions are over, the time has come, it's here it's finally here. | |
0a. In Keystone, we have a conceptual table of consumers, let's call it | |
Consumers. | |
0b. In Keystone, we create an entry in Consumers:: | |
{'consumer_key': 'foo_key', | |
'consumer_secret': 'foo_secret', | |
'name': 'ScaleMe'} | |
0c. Out of band, we tell ScaleMe to use the above consumer key and secret | |
when they use our delegated auth service. | |
1a. Abby tells ScaleMe she wants to authorize it to do its thing, either | |
through a web interface or a command-line interface. | |
1b. ScaleMe makes an Oauth-signed request to Keystone at /oauth/request_token | |
with the `nova:server_launcher` role requested:: | |
/oauth/request_token | |
consumer_key=foo_key | |
&abunchofoauthspecificvariables... | |
&keystone_roles=nova:server_launcher | |
1c. ... and is given a generated string with a funny format:: | |
oauth_token=1234-{suitably_secure_hash:saltybits+'1234'+'5678'} | |
&oauth_token_secret=bar-secret | |
1c. Meanwhile, Keystone stored this in a conceptual Request Tokens table:: | |
{'request_token': 1234-{suitably_secure_hash:saltybits+'1234'+'5678'}, | |
'request_secret': 'bar-secret', | |
'request_verifier': '5678', | |
'user_id': None, | |
'project_id': None, | |
'requested_roles': ['nova:server_launcher'], | |
'consumer_key': 'foo_key', | |
'issued_at': 'someTdatetimeZ', | |
} | |
1d. ScaleMe then responds to Abby, if in a web context it could offer a link | |
to a page or if we're on the command-line it could just provide the | |
request token that she will need to authorize. If Keystone supported it, | |
the a web link would be neato, but let's do the CLI version:: | |
oauth_token=1234-{allthatstufffromabove) | |
&requested_roles=nova:server_launcher | |
1e. Abby makes an authenticated (standard Keystone auth) request to the | |
/oauth/authorize endpoint on Keystone and authorizes it for the requested | |
roles. Keystone returns an authorization code that Abby can give to | |
ScaleMe:: | |
oauth_verifier=5678 | |
1e. Meanwhile, Keystone updates the Request Token record to include Abby's | |
details:: | |
{'request_token': 1234-{suitably_secure_hash:saltybits+'1234'+'5678'}, | |
'request_secret': 'bar-secret', | |
'request_verifier': '5678', | |
'user_id': 'Abby', | |
'project_id': 'abbys_project', | |
'requested_roles': ['nova:server_launcher'], | |
'consumer_key': 'foo_key', | |
'issued_at': 'someTdatetimeZ', | |
} | |
1f. Abby hands the oauth_verifier code to ScaleMe out-of-band and ScaleMe | |
fetches an access token from Keystone at /oauth/acess_token:: | |
/oauth/access_token | |
consumer_key=foo_key | |
&abunchofoauthspecificvariables... | |
&request_token=1234-{allthatstufffromabove} | |
&oauth_verifier=5678 | |
1f. ... and is given the token it will use for future Oauth-signed requests | |
to get new Keystone tokens:: | |
oauth_token=some_uuid | |
&oauth_token_secret=some_other_uuid | |
1f. Meanwhile, Keystone generates the access token and stores it in the db:: | |
{'access_token': some_uuid, | |
'access_secret': some_other_uuid, | |
'user_id': 'Abby', | |
'project_id': 'abbys_project', | |
'roles': ['nova:server_launcher'], | |
'consumer_key': 'foo_key', | |
'issued_at': 'someTdatetimeZ' | |
} | |
2. Whenever ScaleMe has to do something as Abby, it makes a request to | |
Keystone to get to a currently valid Keystone token:: | |
/delegated_auth/token | |
consumer_key=foo_key | |
&abunchofoauthspecificvariables... | |
&access_token=some_uuid | |
Enough detail for ya? | |
Rules Are For Breaking | |
---------------------- | |
There are plenty of shortcuts that can be added to this flow, but the steps | |
above explain the full Oauth flow. | |
Some easily implementable future shortcuts: | |
1. If you are just giving a service you are already running access, you can | |
skip to the end and manually generate a token with the proper roles via | |
some interface in Horizon and just give ScaleMe the access token. | |
2. If Keystone (or possibly Horizon) supports a web interface to the Oauth | |
endpoints, much copy-pasting can be removed and additional information | |
about ScaleMe and the roles it is requesting can be displayed. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
One other question:
The most important thing to keep from the trusts work when migrating over to Oauth is the concept of token chaining. If an oauth RequestToken gets revoked, how do we make sure that any tokens created from that request are also revoked?