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 <[email protected]> Authored: Wed May 11 22:45:30 2016 -0400 Committer: Kapil Arya <[email protected]> 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; }
