Skip to content

Instantly share code, notes, and snippets.

@termie
Last active December 15, 2015 07:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save termie/5225817 to your computer and use it in GitHub Desktop.
Save termie/5225817 to your computer and use it in GitHub Desktop.
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.
@admiyo
Copy link

admiyo commented Mar 26, 2013

A Q&A from an email based on this is here:

https://gist.github.com/admiyo/5247912

@admiyo
Copy link

admiyo commented Mar 26, 2013

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment