d1d5cc8521
Change-Id: I7e20fa968ab08f4e9257623837c1b6c66c74e8b2
306 lines
8.9 KiB
ReStructuredText
306 lines
8.9 KiB
ReStructuredText
..
|
|
This template should be in ReSTructured text. The filename in the git
|
|
repository should match the launchpad URL, for example a URL of
|
|
https://blueprints.launchpad.net/zaqar/+spec/awesome-thing should be named
|
|
awesome-thing.rst.
|
|
|
|
Please do not delete any of the sections in this
|
|
template. If you have nothing to say for a whole section, just write: None
|
|
|
|
For help with syntax, see http://sphinx-doc.org/rest.html
|
|
To test out your formatting, see http://www.tele3.cz/jbar/rest/rest.html
|
|
|
|
======================
|
|
Pre-Signed URLs
|
|
======================
|
|
|
|
https://blueprints.launchpad.net/zaqar/+spec/pre-signed-url
|
|
|
|
There's a need to have pre-signed URLs - ala Swift's tempURL - that
|
|
would grant temporary access, to non-authenticated users, to specific
|
|
queues.
|
|
|
|
Problem description
|
|
===================
|
|
|
|
In cases where there's a need to allow "outsiders" of the system - i.e
|
|
guest-agents - to interact with Zaqar, it's useful to have a way to
|
|
grant them access so that they can do whatever is needed without
|
|
giving these "outsiders" a username and password or any other kind of
|
|
permission in the system.
|
|
|
|
This access, however, needs to be temporary, revocable and
|
|
granular. Outsiders shouldn't get access to more than 1 resource at a
|
|
time and the permissions granted should identify what kind of
|
|
operations they can execute.
|
|
|
|
Proposed change
|
|
===============
|
|
|
|
The proposed solution to the aforementioned problem is to use
|
|
pre-signed URLs. Services like Swift use a similar approach - called
|
|
temp URL there - to provide access to internal resources. The
|
|
pre-signed URL consists of a URL that contains a `hash` with the
|
|
encoded permissions.
|
|
|
|
In order to make it easier to read/write this spec, let's split it in
|
|
several parts. The first part will describe the parts composing the
|
|
URL. The second part will describe how the URL will be generated and
|
|
the third part how it'll be consumed.
|
|
|
|
Pre-Signed URL
|
|
--------------
|
|
|
|
A pre-signed URL ought to contain enough information for it to provide
|
|
enough control over the resource being shared, without compromising
|
|
security. Authorization is a must have and a huge part of this
|
|
URL. Therefore, the information present in this URL has to be
|
|
exhaustive in that perspective.
|
|
|
|
As mentioned in a previous paragraph, the URL contains a hashed piece
|
|
of information that serializes the required fields. Those fields are:
|
|
|
|
* Project: The `keystone` tenant of the entity generating the URL
|
|
* TTL: Expiration time (in seconds) for the URL
|
|
* Queue: `Zaqar`'s queue name that this URL gives access to
|
|
* HTTP Method: The `HTTP` method(s) this `URL` was created for. By
|
|
selecting the HTTP method, it's possible to give either read or
|
|
read/write access to a specific resource.
|
|
|
|
The above fields will be part of the generated hash but they'll also
|
|
be available as public information in the URL. The reason they're
|
|
encoded is to have a way to verify that the URL has not been changed
|
|
by the user whenever a request is made to `Zaqar`.
|
|
|
|
The generated hash will be an HMAC-SHA1. To generate such signature,
|
|
it is required to have a secret key. In swift, it's possible to have a
|
|
key per-account. Unfortunately, that's not possible in Zaqar +
|
|
Keystone, therefore this spec proposes adding a new configuration
|
|
option that will contain the key to use.::
|
|
|
|
[signed_url]
|
|
secret_key = some-very-strong-key
|
|
|
|
|
|
URL Generation
|
|
--------------
|
|
|
|
This spec proposes adding a new endpoint in the queue namespace that
|
|
returns the generated signature and expiration time that'll grant
|
|
access to this resource. The request and response for this operation:
|
|
|
|
Request:::
|
|
|
|
POST /v2/queues/shared_queue/share HTTP/1.1
|
|
...
|
|
|
|
{
|
|
'methods': ['GET', 'POST']
|
|
}
|
|
|
|
Response:::
|
|
|
|
|
|
HTTP/1.0 201 OK
|
|
...
|
|
|
|
{
|
|
'signature': '518b51ea133c4facadae42c328d6b77b',
|
|
'expires': 2015-05-31T19:00:17Z,
|
|
'project': '7d2f63fd4dcc47528e9b1d08f989cc00',
|
|
'url': '/v2/queues/shared_queue/messages',
|
|
'methods': ['GET', 'POST']
|
|
}
|
|
|
|
This request sets a different expiration date for the URL. Note that
|
|
the default method is `GET`.
|
|
|
|
Request:::
|
|
|
|
POST /v2/queues/shared_queue/share HTTP/1.1
|
|
...
|
|
|
|
{
|
|
'expires': 2015-06-19T19:00:00Z
|
|
}
|
|
|
|
Response:::
|
|
|
|
|
|
HTTP/1.0 201 OK
|
|
...
|
|
|
|
{
|
|
'signature': '518b51ea133c4facadae42c328d6b77b',
|
|
'expires': 2015-06-19T19:00:00Z,
|
|
'project': '7d2f63fd4dcc47528e9b1d08f989cc00',
|
|
'url': '/v2/queues/shared_queue/messages'
|
|
'methods': ['GET']
|
|
}
|
|
|
|
|
|
This request combines both parameters (methods and expires):
|
|
|
|
Request:::
|
|
|
|
POST /v2/queues/shared_queue/share HTTP/1.1
|
|
...
|
|
|
|
{
|
|
'methods': ['GET', 'POST'],
|
|
'expires': 2015-06-19T19:00:00Z
|
|
}
|
|
|
|
Response:::
|
|
|
|
|
|
HTTP/1.0 201 OK
|
|
...
|
|
|
|
{
|
|
'signature': '518b51ea133c4facadae42c328d6b77b',
|
|
'expires': 2015-06-19T19:00:00Z,
|
|
'project': '7d2f63fd4dcc47528e9b1d08f989cc00',
|
|
'url': '/v2/queues/shared_queue/messages'
|
|
'methods': ['GET', 'POST']
|
|
}
|
|
|
|
|
|
Consuming the URL
|
|
-----------------
|
|
|
|
First and foremost, it's important to mention that **NONE** of the
|
|
URL headers can/should be modified and/or omitted. As soon as one of
|
|
them is, the signature verification will fail and therefore the
|
|
request will respond 404.
|
|
|
|
Requests for pre-signed URLs will be processed by a middleware that
|
|
should be placed **before** keystone's middleware. This will allow us
|
|
to authenticate the request in advance and skip keystone's
|
|
authentication. A request using the signature generated in the
|
|
previous section would look like:
|
|
|
|
Request:::
|
|
|
|
GET /v2/queues/shared_queue/messages HTTP/1.1
|
|
Host: zaqar.example.com
|
|
User-Agent: python/2.7 killer-rabbit/1.2
|
|
Date: Wed, 28 Nov 2012 21:14:19 GMT
|
|
Accept: application/json
|
|
Accept-Encoding: gzip
|
|
URL-Signature: 518b51ea133c4facadae42c328d6b77b
|
|
URL-Expires: 2015-05-31T19:00:17Z
|
|
X-Project-Id: 7d2f63fd4dcc47528e9b1d08f989cc00
|
|
Client-ID: 30387f00-39a0-11e2-be4d-a8d15f34bae2
|
|
|
|
Note that, in the above example, headers were chosen over query
|
|
parameters. The main 2 reasons behind this choice are:
|
|
|
|
1. Consistency with other security related parameters - i.e
|
|
X-Project-Id - that are sent in HTTP headers.
|
|
|
|
2. These new parameters don't belong in the `messages` request and
|
|
won't affect `messages` navigation.
|
|
|
|
Similarly, other requests like the one below can be done.
|
|
|
|
Request:::
|
|
|
|
GET /v2/queues/shared_queue/messages?marker=1355-237242-783&limit=10 HTTP/1.1
|
|
Host: zaqar.example.com
|
|
User-Agent: python/2.7 killer-rabbit/1.2
|
|
Date: Wed, 28 Nov 2012 21:14:19 GMT
|
|
Accept: application/json
|
|
Accept-Encoding: gzip
|
|
URL-Signature: 518b51ea133c4facadae42c328d6b77b
|
|
URL-Expires: 2015-05-31T19:00:17Z
|
|
X-Project-Id: 7d2f63fd4dcc47528e9b1d08f989cc00
|
|
Client-ID: 30387f00-39a0-11e2-be4d-a8d15f34bae2
|
|
|
|
Filtering and pagination are not part of the signature and fall into
|
|
the `read` permissions that were granted on this.
|
|
|
|
Posting messages will work the same way:
|
|
|
|
Request:::
|
|
|
|
POST /v2/queues/shared_queue/messages HTTP/1.1
|
|
Host: zaqar.example.com
|
|
User-Agent: python/2.7 killer-rabbit/1.2
|
|
Date: Wed, 28 Nov 2012 21:14:19 GMT
|
|
Accept: application/json
|
|
Accept-Encoding: gzip
|
|
URL-Signature: 518b51ea133c4facadae42c328d6b77b
|
|
URL-Expires: 2015-05-31T19:00:17Z
|
|
X-Project-Id: 7d2f63fd4dcc47528e9b1d08f989cc00
|
|
Client-ID: 30387f00-39a0-11e2-be4d-a8d15f34bae2
|
|
|
|
...
|
|
|
|
|
|
Other Notes
|
|
-----------
|
|
|
|
1. In the case of pre-signed URLs, the queue cannot be created
|
|
lazily. This is to prevent cases where queues are deleted and
|
|
users still have a valid URL. This is not a big issues in cases
|
|
where there's just 1 pool. However, if there's a deployment using
|
|
more than 1 type of pool, the lazily created queue may end up in an
|
|
undesired pool and it'd be possible for an attacker to try a DoS on
|
|
that pool. Therefore, whenever a pre-signed URL is created, if a
|
|
pool doesn't exist, one will be created.
|
|
|
|
2. I'm not a fan of passing the `project-id` around but I can't think
|
|
of another way to do this and still have the ability to preserve
|
|
multi-tenancy without passing the project.
|
|
|
|
3. I don't like having the key set in the config file. In future
|
|
versions, we could think of making this information part of the
|
|
queue itself. The reason we can't do that right now is because we
|
|
don't have private fields in the metadata. It should be easy enough
|
|
to do it as an enhancement for this feature.
|
|
|
|
4. As a future enhancement, we could also use Barbican for key management.
|
|
|
|
|
|
Drawbacks
|
|
---------
|
|
|
|
Security issues may be added by this work. We ought to be extra
|
|
careful on reviews and create a vulnerability team that is ready to
|
|
address any issues that might come up.
|
|
|
|
Alternatives
|
|
------------
|
|
|
|
None
|
|
|
|
Implementation
|
|
==============
|
|
|
|
Assignee(s)
|
|
-----------
|
|
|
|
Primary assignee:
|
|
flaper87
|
|
|
|
|
|
Work Items
|
|
----------
|
|
|
|
1. Write utilities to generate the signature with proper tests
|
|
2. Add the endpoint that generates the pre-signed URL
|
|
3. Create a middleware capable of processing these URL
|
|
|
|
Dependencies
|
|
============
|
|
|
|
None
|
|
|
|
.. note::
|
|
|
|
This work is licensed under a Creative Commons Attribution 3.0
|
|
Unported License.
|
|
http://creativecommons.org/licenses/by/3.0/legalcode
|
|
|