You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by ka...@apache.org on 2016/05/12 06:00:47 UTC
[4/7] mesos git commit: Enabled authorization of libprocess HTTP
endpoints (libprocess).
Enabled authorization of libprocess HTTP endpoints (libprocess).
This patch enables libprocess to store and execute
authorization callbacks provided by a client
application.
Review: https://reviews.apache.org/r/46866/
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/25376d8e
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/25376d8e
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/25376d8e
Branch: refs/heads/master
Commit: 25376d8ee9227653a93f546e33be49500b6d9d5c
Parents: acde41a
Author: Greg Mann <gr...@mesosphere.io>
Authored: Wed May 11 22:45:30 2016 -0400
Committer: Kapil Arya <ka...@mesosphere.io>
Committed: Thu May 12 01:50:20 2016 -0400
----------------------------------------------------------------------
3rdparty/libprocess/include/process/http.hpp | 28 +++++
3rdparty/libprocess/src/process.cpp | 129 +++++++++++++++++-----
2 files changed, 129 insertions(+), 28 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mesos/blob/25376d8e/3rdparty/libprocess/include/process/http.hpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/include/process/http.hpp b/3rdparty/libprocess/include/process/http.hpp
index 1732362..10f6fb9 100644
--- a/3rdparty/libprocess/include/process/http.hpp
+++ b/3rdparty/libprocess/include/process/http.hpp
@@ -81,6 +81,34 @@ Future<Nothing> unsetAuthenticator(const std::string& realm);
} // namespace authentication {
+// Forward declaration.
+struct Request;
+
+namespace authorization {
+
+// The `AuthorizationCallbacks` type is used for a set of authorization
+// callbacks used by libprocess to authorize HTTP endpoints. The key of the map
+// contains the endpoint's path, while the value contains the callback.
+typedef hashmap<std::string,
+ lambda::function<process::Future<bool>(
+ const Request,
+ const Option<std::string> principal)>>
+ AuthorizationCallbacks;
+
+
+// Set authorization callbacks for HTTP endpoints. These can be used to call out
+// to an external, application-level authorizer. The callbacks should accept an
+// HTTP request and an optional principal, and they should return a
+// `Future<bool>` representing whether or not authorization was successful.
+void setCallbacks(const AuthorizationCallbacks&);
+
+
+// Remove any authorization callbacks which were previously installed in
+// libprocess.
+void unsetCallbacks();
+
+} // namespace authorization {
+
// Status code reason strings, from the HTTP1.1 RFC:
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html
extern hashmap<uint16_t, std::string>* statuses;
http://git-wip-us.apache.org/repos/asf/mesos/blob/25376d8e/3rdparty/libprocess/src/process.cpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/src/process.cpp b/3rdparty/libprocess/src/process.cpp
index becd263..988fb3b 100644
--- a/3rdparty/libprocess/src/process.cpp
+++ b/3rdparty/libprocess/src/process.cpp
@@ -131,6 +131,8 @@ using process::http::authentication::Authenticator;
using process::http::authentication::AuthenticationResult;
using process::http::authentication::AuthenticatorManager;
+using process::http::authorization::AuthorizationCallbacks;
+
using process::network::Address;
using process::network::Socket;
@@ -484,6 +486,9 @@ static Gate* gate = new Gate();
// Used for authenticating HTTP requests.
static AuthenticatorManager* authenticator_manager = NULL;
+// Authorization callbacks for HTTP endpoints.
+static AuthorizationCallbacks* authorization_callbacks = NULL;
+
// Filter. Synchronized support for using the filterer needs to be
// recursive in case a filterer wants to do anything fancy (which is
// possible and likely given that filters will get used for testing).
@@ -504,6 +509,7 @@ THREAD_LOCAL Executor* _executor_ = NULL;
namespace http {
+
namespace authentication {
Future<Nothing> setAuthenticator(
@@ -524,6 +530,22 @@ Future<Nothing> unsetAuthenticator(const string& realm)
}
} // namespace authentication {
+
+namespace authorization {
+
+void setCallbacks(const AuthorizationCallbacks& callbacks)
+{
+ authorization_callbacks = new AuthorizationCallbacks(callbacks);
+}
+
+
+void unsetCallbacks()
+{
+ authorization_callbacks = NULL;
+}
+
+} // namespace authorization {
+
} // namespace http {
@@ -3226,7 +3248,7 @@ void ProcessBase::visit(const HttpEvent& event)
<< " with path: '" << event.request->url.path << "'";
// Lazily initialize the Sequence needed for ordering requests
- // across authentication.
+ // across authentication and authorization.
if (handlers.httpSequence.get() == NULL) {
handlers.httpSequence.reset(new Sequence());
}
@@ -3236,17 +3258,22 @@ void ProcessBase::visit(const HttpEvent& event)
// Split the path by '/'.
vector<string> tokens = strings::tokenize(event.request->url.path, "/");
CHECK(!tokens.empty());
- CHECK_EQ(pid.id, http::decode(tokens[0]).get());
+
+ const string id = http::decode(tokens[0]).get();
+ CHECK_EQ(pid.id, id);
// First look to see if there is an HTTP handler that can handle the
// longest prefix of this path.
- // Remove the 'id' prefix from the path.
+ // Remove the `id` prefix from the path.
string name = strings::remove(
event.request->url.path, "/" + tokens[0], strings::PREFIX);
-
name = strings::trim(name, strings::PREFIX, "/");
+ // Look for an endpoint handler for this path. We begin with the full path,
+ // but if no handler is found and the path is nested, we shorten it and look
+ // again. For example: if the request is for '/a/b/c' and no handler is found,
+ // we will then check for '/a/b', and finally for '/a'.
while (Path(name).dirname() != name) {
if (handlers.http.count(name) == 0) {
name = Path(name).dirname();
@@ -3271,7 +3298,7 @@ void ProcessBase::visit(const HttpEvent& event)
event.response->associate(response->future());
authentication
- .onAny(defer(self(), [endpoint, request, response](
+ .onAny(defer(self(), [this, endpoint, request, response, name, id](
const Future<Option<AuthenticationResult>>& authentication) {
if (!authentication.isReady()) {
response->set(InternalServerError());
@@ -3287,36 +3314,82 @@ void ProcessBase::visit(const HttpEvent& event)
return;
}
- if (authentication->isNone()) {
- // Request didn't need authentication or authentication
- // is not applicable, just forward the request.
- if (endpoint.realm.isNone()) {
- response->associate(endpoint.handler.get()(request));
- } else {
- response->associate(endpoint.authenticatedHandler.get()(
- request, None()));
+ Option<string> principal = None();
+
+ // If authentication failed, we do not continue with authorization.
+ if (authentication->isSome()) {
+ if (authentication.get()->unauthorized.isSome()) {
+ // Request was not authenticated, challenged issued.
+ response->set(authentication.get()->unauthorized.get());
+
+ delete response;
+ return;
+ } else if (authentication.get()->forbidden.isSome()) {
+ // Request was not authenticated, no challenge issued.
+ response->set(authentication.get()->forbidden.get());
+
+ delete response;
+ return;
}
- delete response;
- return;
+ principal = authentication.get()->principal;
}
- if (authentication.get()->unauthorized.isSome()) {
- // Request was not authenticated, challenged issued.
- response->set(authentication.get()->unauthorized.get());
- } else if (authentication.get()->forbidden.isSome()) {
- // Request was not authenticated, no challenge issued.
- response->set(authentication.get()->forbidden.get());
+ // The result of a call to an authorization callback.
+ Future<bool> authorization;
+
+ // Look for an authorization callback installed for this endpoint path.
+ // If none is found, use a trivial one.
+ const string callback_path = path::join("/" + id, name);
+ if (authorization_callbacks != NULL &&
+ authorization_callbacks->count(callback_path) > 0) {
+ authorization = authorization_callbacks->at(callback_path)(
+ request, principal);
+
+ // Sequence the authorization future to ensure the handlers
+ // are invoked in the same order that requests arrive.
+ authorization = handlers.httpSequence->add<bool>(
+ [authorization]() { return authorization; });
} else {
- Option<string> principal = authentication.get()->principal;
-
- response->associate(endpoint.authenticatedHandler.get()(
- request, principal));
+ authorization = handlers.httpSequence->add<bool>(
+ []() { return true; });
}
- delete response;
- return;
- }));
+ // Install a callback on the authorization result.
+ authorization
+ .onAny(defer(self(), [endpoint, request, response, principal](
+ const Future<bool>& authorization) {
+ if (!authorization.isReady()) {
+ response->set(InternalServerError());
+
+ VLOG(1) << "Returning '" << response->future()->status << "'"
+ << " for '" << request.url.path << "'"
+ << " (authorization failed: "
+ << (authorization.isFailed()
+ ? authorization.failure()
+ : "discarded") << ")";
+
+ delete response;
+ return;
+ }
+
+ if (authorization.get() == true) {
+ // Authorization succeeded, so forward request to the handler.
+ if (endpoint.realm.isNone()) {
+ response->associate(endpoint.handler.get()(request));
+ } else {
+ response->associate(endpoint.authenticatedHandler.get()(
+ request, principal));
+ }
+ } else {
+ // Authorization failed, so return a `Forbidden` response.
+ response->set(Forbidden());
+ }
+
+ delete response;
+ return;
+ }));
+ }));
return;
}