Repository: mesos Updated Branches: refs/heads/master 52cf9b3ff -> a5cc9b435
Added authorization support for "/shutdown" endpoint and updated ACLs format. Project: http://git-wip-us.apache.org/repos/asf/mesos/repo Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/a5cc9b43 Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/a5cc9b43 Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/a5cc9b43 Branch: refs/heads/master Commit: a5cc9b435aad080a79230f0366a6ce77116c95a4 Parents: 94b9439 Author: Benjamin Hindman <[email protected]> Authored: Fri Aug 8 16:53:12 2014 -0700 Committer: Vinod Kone <[email protected]> Committed: Fri Aug 8 17:03:17 2014 -0700 ---------------------------------------------------------------------- docs/authorization.md | 129 +++++++------ include/mesos/mesos.proto | 62 +++--- src/authorizer/authorizer.cpp | 93 +++------ src/authorizer/authorizer.hpp | 18 +- src/credentials/credentials.hpp | 6 +- src/master/flags.hpp | 41 ++-- src/master/http.cpp | 124 +++++++++--- src/master/master.cpp | 4 +- src/master/master.hpp | 10 + src/sasl/authenticator.hpp | 6 +- src/tests/authorization_tests.cpp | 260 ++++---------------------- src/tests/master_authorization_tests.cpp | 30 +-- src/tests/mesos.cpp | 5 +- src/tests/mesos.hpp | 17 +- src/tests/reconciliation_tests.cpp | 2 +- src/tests/script.cpp | 8 +- src/tests/shutdown_tests.cpp | 109 ++++++++++- 17 files changed, 425 insertions(+), 499 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mesos/blob/a5cc9b43/docs/authorization.md ---------------------------------------------------------------------- diff --git a/docs/authorization.md b/docs/authorization.md index fea743a..6697944 100644 --- a/docs/authorization.md +++ b/docs/authorization.md @@ -6,11 +6,10 @@ layout: documentation Mesos 0.20.0 adds support for framework authorization. Authorization allows - 1. Frameworks to only launch tasks/executors as authorized `users`. - 2. Frameworks to be able to receive offers for authorized `roles`. - 3. HTTP endpoints exposed by Mesos to be accessible to authorized `clients`. + 1. Frameworks to (re-)register with authorized `roles`. + 2. Frameworks to launch tasks/executors as authorized `users`. + 3. Authorized `principals` to shutdown framework(s) through "/shutdown" HTTP endpoint. -> NOTE: While ACLs support for HTTP is present, currently access to the HTTP endpoints are not authorized. ## ACLs @@ -20,40 +19,41 @@ Each ACL specifies a set of `Subjects` that can perform an `Action` on a set of The currently supported `Actions` are : -1. "run_tasks" : Run tasks/executors -2. "receive_offers" : Receive offers -3. "http_get" : HTTP GET access -4. "http_put" : HTTP_PUT access +1. "register_frameworks" : Register Frameworks +2. "run_tasks" : Run tasks/executors +3. "shutdown_frameworks" : Shutdown frameworks The currently supported `Subjects` are : -1. "principals" : Framework principals (used by "run_tasks" and "receive_offers" actions) -2. "usernames" : Username used in HTTP Basic/Digest authentication. (used by "http_get" and "http_put" actions) -3. "ips" : IP Addresses of the clients (used by "http_get" and "http_put" actions) -4. "hostnames" : Hostnames of the clients (used by "http_get" and "http_put" actions) +1. "principals" + - Framework principals (used by "register_frameworks" and "run_tasks" actions) + - Usernames (used by "shutdown_frameworks" action) The currently supported `Objects` are : -1. "users" : Unix user to launch the task/executor as (used by "run_tasks" action) -2. "roles" : Resource roles to receive offers from (used by "receive_offers" action) -3. "urls" : HTTP URL endpoint exposed by the master (used by "http_get" and "http_put" actions) +1. "roles" : Resource roles that framework can register with (used by "register_frameworks" action) +2. "users" : Unix user to launch the task/executor as (used by "run_tasks" action) +3. "framework_principals" : Framework principals that can be shutdown by HTTP POST (used by "shutdown_frameworks" action). -> NOTE: Both `Subjects` and `Objects` can take a list of strings or special values (`ANY` and `NONE`). +> NOTE: Both `Subjects` and `Objects` can take a list of strings or special values (`ANY` or `NONE`). ## How does it work? -The Mesos master checks the ACLs to verify whether a request is authorized or not. For example, when a framework launches a task, "run_tasks" ACLs are checked to see if the framework (`FrameworkInfo.principal`) is authorized to run the task/executor as the given user. If not authorized, the launch is rejected and the framework gets a TASK_LOST. +The Mesos master checks the ACLs to verify whether a request is authorized or not. -Similarly, when a framework (re-)registers the Mesos master checks whether it is authorized to receive offers for given resource role (`FrameworkInfo.role`). If not authorized, the framework is not allowed to (re-)register and gets an Error message back. +For example, when a framework (re-)registers with the master, the "register_frameworks" ACLs are checked to see if the framework (`FrameworkInfo.principal`) is authorized to receive offers for the given resource role (`FrameworkInfo.role`). If not authorized, the framework is not allowed to (re-)register and gets an `Error` message back (which aborts the scheduler driver). + +Similarly, when a framework launches a task(s), "run_tasks" ACLs are checked to see if the framework (`FrameworkInfo.principal`) is authorized to run the task/executor as the given `user`. If not authorized, the launch is rejected and the framework gets a TASK_LOST. + +In the same vein, when a user/principal attempts to shutdown a framework through the "/shutdown" HTTP endpoint on the master, "shutdown_frameworks" ACLs are checked to see if the `principal` is authorized to shutdown the given framework. If not authorized, the shutdown is rejected and the user receives an `Unauthorized` HTTP response. -While not yet implemented, GET/PUT access to HTTP endpoints exposed by the Mesos master will be authorized in a similar way. There are couple of important things to note: 1. ACLs are matched in the order that they are setup. In other words, the first matching ACL determines whether a request is authorized or not. -2. If none of the specified ACLs match the given request, whether the request is authorized or not is defined by `ACLs.permissive` field. By default this "true" i.e., a non-matching request is authorized. +2. If none of the specified ACLs match the given request, whether the request is authorized or not is defined by `ACLs.permissive` field. By default this is "true" i.e., a non-matching request is authorized. ## Examples @@ -66,7 +66,7 @@ There are couple of important things to note: "principals": { "values": ["foo", "bar"] }, "users": { "values": ["alice"] } } - ], + ] } 2. Any framework can run tasks as user `guest`. @@ -77,7 +77,7 @@ There are couple of important things to note: "principals": { "type": "ANY" }, "users": { "values": ["guest"] } } - ], + ] } 3. No framework can run tasks as `root`. @@ -88,7 +88,7 @@ There are couple of important things to note: "principals": { "type": "NONE" }, "users": { "values": ["root"] } } - ], + ] } @@ -102,52 +102,66 @@ There are couple of important things to note: }, { "principals": { "values": [ "foo" ] }, - "users": { "type": ["NONE"] } + "users": { "type": "NONE" } } - ], + ] } -5. Framework `foo` can be offered resources for `analytics` and `ads` roles. +5. Framework `foo` can register with `analytics` and `ads` roles. { - "receive_offers": [ - { - "principals": { "values": ["foo"] }, - "roles": { "values": ["analytics", "ads"] } - } - ], + "register_frameworks": [ + { + "principals": { "values": ["foo"] }, + "roles": { "values": ["analytics", "ads"] } + } + ] } -6. Only framework `foo` and no one else can be offered resources for `analytics` role. +6. Only framework `foo` and no one else can register with `analytics` role. { - "receive_offers": [ - { - "principals": { "values": ["foo"] }, - "roles": { "values": ["analytics"] } - }, - { - "principals": { "type": "NONE" }, - "roles": { "values": ["analytics"] } - } - ], + "register_frameworks": [ + { + "principals": { "values": ["foo"] }, + "roles": { "values": ["analytics"] } + }, + { + "principals": { "type": "NONE" }, + "roles": { "values": ["analytics"] } + } + ] } -7. Framework `foo` can only receive offers for `analytics` role but no other roles. Also, no other framework can receive offers for any role. +7. Framework `foo` can only register with `analytics` role but no other roles. Also, no other framework can register with any roles. { "permissive" : "false", - "receive_offers": [ - { - "principals": { "values": ["foo"] }, - "roles": { "values": ["analytics"] } - } - ], + "register_frameworks": [ + { + "principals": { "values": ["foo"] }, + "roles": { "values": ["analytics"] } + } + ] + } + + +8. Only `ops` principal can shutdown any frameworks through "/shutdown" HTTP endpoint. + + { + "permissive" : "false", + + "shutdown_frameworks": [ + { + "principals": { "values": ["ops"] }, + "framework_principals": { "type": "ANY" } + } + ] } @@ -161,20 +175,5 @@ As part of this feature, a new flag was added to the master. or '/path/to/file'. See the ACLs protobuf in mesos.proto for the expected format. - Example: - { - "run_tasks": [ - { - "principals": { "values": ["a", "b"] }, - "users": { "values": ["root"] } - } - ], - "receive_offers": [ - { - "principals": { "type": "ANY" }, - "roles": { "values": ["foo"] } - } - ] - } **For the complete list of master options: ./mesos-master.sh --help** http://git-wip-us.apache.org/repos/asf/mesos/blob/a5cc9b43/include/mesos/mesos.proto ---------------------------------------------------------------------- diff --git a/include/mesos/mesos.proto b/include/mesos/mesos.proto index efb4239..cc9f20e 100644 --- a/include/mesos/mesos.proto +++ b/include/mesos/mesos.proto @@ -658,12 +658,13 @@ message Parameters { /** - * Credential used for authentication. + * Credential used in various places for authentication and + * authorization. * - * NOTE: The 'principal' is used for authenticating the framework or slave - * with the master. This is different from 'FrameworkInfo.user' - * which is used to determine the user under which the framework's - * executors/tasks are run. + * NOTE: A 'principal' is different from 'FrameworkInfo.user'. The + * former is used for authentication and authorization while the + * latter is used to determine the default user under which the + * framework's executors/tasks are run. */ message Credential { required string principal = 1; @@ -672,18 +673,12 @@ message Credential { /** - * Credentials used for authentication. - * + * Credentials used for framework authentication, HTTP authentication + * (where the common 'username' and 'password' are captured as + * 'principal' and 'secret' respectively), etc. */ message Credentials { - // Collection of credentials used to authenticate "registration" of - // either frameworks or slaves. - repeated Credential registration = 1; - - // Collection of credentails used to authenticate HTTP endpoints - // (where the common "username" and "password" are captured as - // 'principal' and 'secret' respectively). - repeated Credential http = 2; + repeated Credential credentials = 1; } @@ -707,40 +702,30 @@ message ACL { } // ACLs. - message RunTasks { + message RegisterFramework { // Subjects. required Entity principals = 1; // Framework principals. // Objects. - required Entity users = 2; // Users to run the tasks/executors as. + required Entity roles = 2; // Roles for resource offers. } - message ReceiveOffers { + message RunTask { // Subjects. required Entity principals = 1; // Framework principals. // Objects. - required Entity roles = 2; // Resource roles that can be offered. - } - - message HTTPGet { - // Subjects (At least one of these should be set). - optional Entity usernames = 1; // HTTP authentication based usernames. - optional Entity ips = 2; - optional Entity hostnames = 3; - - // Objects. - required Entity urls = 4; + required Entity users = 2; // Users to run the tasks/executors as. } - message HTTPPut { - // Subjects (At least one of these should be set). - optional Entity usernames = 1; // HTTP authentication based usernames. - optional Entity ips = 2; - optional Entity hostnames = 3; + // Which principals are authorized to shutdown frameworks of other + // principals. + message ShutdownFramework { + // Subjects. + required Entity principals = 1; // Objects. - required Entity urls = 4; + required Entity framework_principals = 2; } } @@ -766,10 +751,9 @@ message ACL { */ message ACLs { optional bool permissive = 1 [default = true]; - repeated ACL.RunTasks run_tasks = 2; - repeated ACL.ReceiveOffers receive_offers = 3; - repeated ACL.HTTPGet http_get = 4; - repeated ACL.HTTPPut http_put = 5; + repeated ACL.RegisterFramework register_frameworks = 2; + repeated ACL.RunTask run_tasks = 3; + repeated ACL.ShutdownFramework shutdown_frameworks = 4; } http://git-wip-us.apache.org/repos/asf/mesos/blob/a5cc9b43/src/authorizer/authorizer.cpp ---------------------------------------------------------------------- diff --git a/src/authorizer/authorizer.cpp b/src/authorizer/authorizer.cpp index 40a14be..21e97e3 100644 --- a/src/authorizer/authorizer.cpp +++ b/src/authorizer/authorizer.cpp @@ -54,73 +54,53 @@ public: LocalAuthorizerProcess(const ACLs& _acls) : ProcessBase(process::ID::generate("authorizer")), acls(_acls) {} - Future<bool> authorize(const ACL::RunTasks& request) + Future<bool> authorize(const ACL::RegisterFramework& request) { - foreach (const ACL::RunTasks& acl, acls.run_tasks()) { + foreach (const ACL::RegisterFramework& acl, acls.register_frameworks()) { // ACL matches if both subjects and objects match. if (matches(request.principals(), acl.principals()) && - matches(request.users(), acl.users())) { + matches(request.roles(), acl.roles())) { // ACL is allowed if both subjects and objects are allowed. return allows(request.principals(), acl.principals()) && - allows(request.users(), acl.users()); + allows(request.roles(), acl.roles()); } } return acls.permissive(); // None of the ACLs match. } - Future<bool> authorize(const ACL::ReceiveOffers& request) + Future<bool> authorize(const ACL::RunTask& request) { - foreach (const ACL::ReceiveOffers& acl, acls.receive_offers()) { + foreach (const ACL::RunTask& acl, acls.run_tasks()) { // ACL matches if both subjects and objects match. if (matches(request.principals(), acl.principals()) && - matches(request.roles(), acl.roles())) { + matches(request.users(), acl.users())) { // ACL is allowed if both subjects and objects are allowed. return allows(request.principals(), acl.principals()) && - allows(request.roles(), acl.roles()); + allows(request.users(), acl.users()); } } return acls.permissive(); // None of the ACLs match. } - Future<bool> authorize(const ACL::HTTPGet& request) + Future<bool> authorize(const ACL::ShutdownFramework& request) { - foreach (const ACL::HTTPGet& acl, acls.http_get()) { + foreach (const ACL::ShutdownFramework& acl, acls.shutdown_frameworks()) { // ACL matches if both subjects and objects match. - if (matches(request.usernames(), acl.usernames()) && - matches(request.ips(), acl.ips()) && - matches(request.hostnames(), acl.hostnames()) && - matches(request.urls(), acl.urls())) { + if (matches(request.principals(), acl.principals()) && + matches(request.framework_principals(), + acl.framework_principals())) { // ACL is allowed if both subjects and objects are allowed. - return allows(request.usernames(), acl.usernames()) && - allows(request.ips(), acl.ips()) && - allows(request.hostnames(), acl.hostnames()) && - allows(request.urls(), acl.urls()); + return allows(request.principals(), acl.principals()) && + allows(request.framework_principals(), + acl.framework_principals()); } } return acls.permissive(); // None of the ACLs match. } - Future<bool> authorize(const ACL::HTTPPut& request) - { - foreach (const ACL::HTTPPut& acl, acls.http_put()) { - // ACL matches if both subjects and objects match. - if (matches(request.usernames(), acl.usernames()) && - matches(request.ips(), acl.ips()) && - matches(request.hostnames(), acl.hostnames()) && - matches(request.urls(), acl.urls())) { - // ACL is allowed if both subjects and objects are allowed. - return allows(request.usernames(), acl.usernames()) && - allows(request.ips(), acl.ips()) && - allows(request.hostnames(), acl.hostnames()) && - allows(request.urls(), acl.urls()); - } - } - - return acls.permissive(); // None of the ACLs match. - } private: // Match matrix: // @@ -263,61 +243,36 @@ LocalAuthorizer::~LocalAuthorizer() Try<Owned<LocalAuthorizer> > LocalAuthorizer::create(const ACLs& acls) { - // Validate ACLs. - foreach (const ACL::HTTPGet& acl, acls.http_get()) { - // At least one of the subjects should be set. - if (acl.has_usernames() + acl.has_ips() + acl.has_hostnames() < 1) { - return Error("At least one of the subjects should be set for ACL: " + - acl.DebugString()); - } - } - - foreach (const ACL::HTTPPut& acl, acls.http_put()) { - // At least one of the subjects should be set. - if (acl.has_usernames() + acl.has_ips() + acl.has_hostnames() < 1) { - return Error("At least one of the subjects should be set for ACL: " + - acl.DebugString()); - } - } - return new LocalAuthorizer(acls); } -Future<bool> LocalAuthorizer::authorize(const ACL::RunTasks& request) -{ - // Necessary to disambiguate. - typedef Future<bool>(LocalAuthorizerProcess::*F)(const ACL::RunTasks&); - - return dispatch( - process, static_cast<F>(&LocalAuthorizerProcess::authorize), request); -} - - -Future<bool> LocalAuthorizer::authorize(const ACL::ReceiveOffers& request) +Future<bool> LocalAuthorizer::authorize(const ACL::RegisterFramework& request) { // Necessary to disambiguate. - typedef Future<bool>(LocalAuthorizerProcess::*F)(const ACL::ReceiveOffers&); + typedef Future<bool>(LocalAuthorizerProcess::*F)( + const ACL::RegisterFramework&); return dispatch( process, static_cast<F>(&LocalAuthorizerProcess::authorize), request); } -Future<bool> LocalAuthorizer::authorize(const ACL::HTTPGet& request) +Future<bool> LocalAuthorizer::authorize(const ACL::RunTask& request) { // Necessary to disambiguate. - typedef Future<bool>(LocalAuthorizerProcess::*F)(const ACL::HTTPGet&); + typedef Future<bool>(LocalAuthorizerProcess::*F)(const ACL::RunTask&); return dispatch( process, static_cast<F>(&LocalAuthorizerProcess::authorize), request); } -Future<bool> LocalAuthorizer::authorize(const ACL::HTTPPut& request) +Future<bool> LocalAuthorizer::authorize(const ACL::ShutdownFramework& request) { // Necessary to disambiguate. - typedef Future<bool>(LocalAuthorizerProcess::*F)(const ACL::HTTPPut&); + typedef Future<bool>(LocalAuthorizerProcess::*F)( + const ACL::ShutdownFramework&); return dispatch( process, static_cast<F>(&LocalAuthorizerProcess::authorize), request); http://git-wip-us.apache.org/repos/asf/mesos/blob/a5cc9b43/src/authorizer/authorizer.hpp ---------------------------------------------------------------------- diff --git a/src/authorizer/authorizer.hpp b/src/authorizer/authorizer.hpp index b7f1119..c039d94 100644 --- a/src/authorizer/authorizer.hpp +++ b/src/authorizer/authorizer.hpp @@ -49,13 +49,11 @@ public: // A failed future indicates a transient failure and the user // can (should) retry. virtual process::Future<bool> authorize( - const ACL::RunTasks& request) = 0; + const ACL::RegisterFramework& request) = 0; virtual process::Future<bool> authorize( - const ACL::ReceiveOffers& request) = 0; + const ACL::RunTask& request) = 0; virtual process::Future<bool> authorize( - const ACL::HTTPGet& request) = 0; - virtual process::Future<bool> authorize( - const ACL::HTTPPut& request) = 0; + const ACL::ShutdownFramework& request) = 0; protected: Authorizer() {} @@ -71,10 +69,12 @@ public: virtual ~LocalAuthorizer(); // Implementation of Authorizer interface. - virtual process::Future<bool> authorize(const ACL::RunTasks& request); - virtual process::Future<bool> authorize(const ACL::ReceiveOffers& request); - virtual process::Future<bool> authorize(const ACL::HTTPGet& request); - virtual process::Future<bool> authorize(const ACL::HTTPPut& request); + virtual process::Future<bool> authorize( + const ACL::RegisterFramework& request); + virtual process::Future<bool> authorize( + const ACL::RunTask& request); + virtual process::Future<bool> authorize( + const ACL::ShutdownFramework& request); private: LocalAuthorizer(const ACLs& acls); http://git-wip-us.apache.org/repos/asf/mesos/blob/a5cc9b43/src/credentials/credentials.hpp ---------------------------------------------------------------------- diff --git a/src/credentials/credentials.hpp b/src/credentials/credentials.hpp index 1790793..4cdadb1 100644 --- a/src/credentials/credentials.hpp +++ b/src/credentials/credentials.hpp @@ -53,7 +53,7 @@ inline Result<Credentials> read(const std::string& path) << "credentials file is NOT accessible by others."; } - // TODO(ijimenez) deprecate text support only JSON like acls + // TODO(ijimenez): Deprecate text and support only JSON like ACLs. Try<JSON::Object> json = JSON::parse<JSON::Object>(read.get()); if (!json.isError()) { Try<Credentials> credentials = ::protobuf::parse<Credentials>(json.get()); @@ -67,11 +67,11 @@ inline Result<Credentials> read(const std::string& path) const std::vector<std::string>& pairs = strings::tokenize(line, " "); if (pairs.size() != 2) { return Error("Invalid credential format at line " + - stringify(credentials.registration().size() + 1)); + stringify(credentials.credentials().size() + 1)); } // Add the credential. - Credential* credential = credentials.add_registration(); + Credential* credential = credentials.add_credentials(); credential->set_principal(pairs[0]); credential->set_secret(pairs[1]); } http://git-wip-us.apache.org/repos/asf/mesos/blob/a5cc9b43/src/master/flags.hpp ---------------------------------------------------------------------- diff --git a/src/master/flags.hpp b/src/master/flags.hpp index 0db4c95..5e9ecb5 100644 --- a/src/master/flags.hpp +++ b/src/master/flags.hpp @@ -213,24 +213,17 @@ public: "Either a path to a text file with a list of credentials,\n" "each line containing 'principal' and 'secret' separated by " "whitespace,\n" - "or, a path to a JSON-formatted file containing credentials\n" - "for identification/registration and http authentication." + "or, a path to a JSON-formatted file containing credentials.\n" "Path could be of the form 'file:///path/to/file' or '/path/to/file'." "\n" "JSON file Example:\n" "{\n" - " \"http\": [\n" - " {\n" - " \"principal\": \"username\",\n" - " \"secret\": \"secret\",\n" - " }\n" - " ],\n" - " \"identification\": [\n" - " {\n" - " \"principal\": \"username\",\n" - " \"secret\": \"secret\",\n" - " }\n" - " ]\n" + " \"credentials\": [\n" + " {\n" + " \"principal\": \"sherman\",\n" + " \"secret\": \"kitesurf\",\n" + " }\n" + " ]\n" "}\n" "Text file Example:\n" "username secret\n" @@ -247,18 +240,24 @@ public: "\n" "Example:\n" "{\n" + " \"register_frameworks\": [\n" + " {\n" + " \"principals\": { \"type\": \"ANY\" },\n" + " \"roles\": { \"values\": [\"a\"] }\n" + " }\n" + " ],\n" " \"run_tasks\": [\n" " {\n" " \"principals\": { \"values\": [\"a\", \"b\"] },\n" - " \"users\": { \"values\": [\"root\"] }\n" + " \"users\": { \"values\": [\"c\"] }\n" " }\n" " ],\n" - " \"receive_offers\": [\n" - " {\n" - " \"principals\": { \"type\": \"ANY\" },\n" - " \"roles\": { \"values\": [\"foo\"] }\n" - " }\n" - " ]\n" + " \"shutdown_frameworks\": [\n" + " {\n" + " \"principals\": { \"values\": [\"a\", \"b\"] },\n" + " \"framework_principals\": { \"values\": [\"c\"] }\n" + " }\n" + " ]\n" "}"); add(&Flags::rate_limits, http://git-wip-us.apache.org/repos/asf/mesos/blob/a5cc9b43/src/master/http.cpp ---------------------------------------------------------------------- diff --git a/src/master/http.cpp b/src/master/http.cpp index f2ca659..9317a95 100644 --- a/src/master/http.cpp +++ b/src/master/http.cpp @@ -39,6 +39,8 @@ #include <stout/result.hpp> #include <stout/strings.hpp> +#include "authorizer/authorizer.hpp" + #include "common/attributes.hpp" #include "common/build.hpp" #include "common/http.hpp" @@ -620,42 +622,80 @@ const string Master::Http::SHUTDOWN_HELP = HELP( Future<Response> Master::Http::shutdown(const Request& request) { - if (master->credentials.isNone()) { - return Unauthorized("Mesos master"); + if (request.method != "POST") { + return BadRequest("Expecting POST"); } + + // Parse the query string in the request body (since this is a POST) + // in order to determine the framework ID to shutdown. hashmap<string, string> values = process::http::query::parse(request.body); - Option<string> frameworkId = values.get("frameworkId"); - if (frameworkId.isNone()) { - return BadRequest(); + + if (values.get("frameworkId").isNone()) { + return BadRequest("Missing 'frameworkId' query parameter"); } + FrameworkID id; - id.set_value(frameworkId.get()); + id.set_value(values.get("frameworkId").get()); + Framework* framework = master->getFramework(id); - Option<string> authHeader = request.headers.get("Authorization"); - if (authHeader.isNone()) { - return Unauthorized("Mesos master"); + if (framework == NULL) { + return BadRequest("No framework found with specified ID"); } - const string& decodedAuth = - base64::decode(strings::split(authHeader.get(), " ", 2)[1]); - const std::vector<string>& pairs = strings::split(decodedAuth, ":", 2); - if (pairs.size() != 2) { + + Result<Credential> credential = authenticate(request); + + if (credential.isError()) { + return Unauthorized("Mesos master", credential.error()); + } + + // Skip authorization if no ACLs were provided to the master. + if (master->authorizer.isNone()) { + return _shutdown(id); + } + + mesos::ACL::ShutdownFramework shutdown; + + if (credential.isSome()) { + shutdown.mutable_principals()->add_values(credential.get().principal()); + } else { + shutdown.mutable_principals()->set_type(ACL::Entity::ANY); + } + + if (framework->info.has_principal()) { + shutdown.mutable_framework_principals()->add_values( + framework->info.principal()); + } else { + shutdown.mutable_framework_principals()->set_type(ACL::Entity::ANY); + } + + lambda::function<Future<Response>(bool)> _shutdown = + lambda::bind(&Master::Http::_shutdown, this, id, lambda::_1); + + return master->authorizer.get()->authorize(shutdown) + .then(defer(master->self(), _shutdown)); +} + + +Future<Response> Master::Http::_shutdown( + const FrameworkID& id, + bool authorized) +{ + if (!authorized) { return Unauthorized("Mesos master"); } - const string& username = pairs[0]; - const string& password = pairs[1]; + Framework* framework = master->getFramework(id); - foreach (const Credential& credential, master->credentials.get().http()) { - if (credential.principal() == username && - (!credential.has_secret() || credential.secret() == password)) { - // TODO(ijimenez) make removeFramework asynchronously - master->removeFramework(framework); - return OK(); - } + if (framework == NULL) { + return BadRequest("No framework found with ID " + stringify(id)); } - return Unauthorized("Mesos master"); + + // TODO(ijimenez): Do 'removeFramework' asynchronously. + master->removeFramework(framework); + + return OK(); } @@ -780,6 +820,44 @@ Future<Response> Master::Http::tasks(const Request& request) } +Result<Credential> Master::Http::authenticate(const Request& request) +{ + // By default, assume everyone is authenticated if no credentials + // were provided. + if (master->credentials.isNone()) { + return None(); + } + + Option<string> authorization = request.headers.get("Authorization"); + + if (authorization.isNone()) { + return Error("Missing 'Authorization' request header"); + } + + const string& decoded = + base64::decode(strings::split(authorization.get(), " ", 2)[1]); + + const vector<string>& pairs = strings::split(decoded, ":", 2); + + if (pairs.size() != 2) { + return Error("Malformed 'Authorization' request header"); + } + + const string& username = pairs[0]; + const string& password = pairs[1]; + + foreach (const Credential& credential, + master->credentials.get().credentials()) { + if (credential.principal() == username && + credential.secret() == password) { + return credential; + } + } + + return Error("Could not authenticate '" + username + "'"); +} + + } // namespace master { } // namespace internal { } // namespace mesos { http://git-wip-us.apache.org/repos/asf/mesos/blob/a5cc9b43/src/master/master.cpp ---------------------------------------------------------------------- diff --git a/src/master/master.cpp b/src/master/master.cpp index e688b41..d53d6c2 100644 --- a/src/master/master.cpp +++ b/src/master/master.cpp @@ -1276,7 +1276,7 @@ Future<Option<Error> > Master::validate( << "Authorizing framework principal '" << frameworkInfo.principal() << "' to receive offers for role '" << frameworkInfo.role() << "'"; - mesos::ACL::ReceiveOffers request; + mesos::ACL::RegisterFramework request; if (frameworkInfo.has_principal()) { request.mutable_principals()->add_values(frameworkInfo.principal()); } else { @@ -2286,7 +2286,7 @@ Future<Option<Error> > Master::validateTask( << "Authorizing framework principal '" << framework->info.principal() << "' to launch task " << task.task_id() << " as user '" << user << "'"; - mesos::ACL::RunTasks request; + mesos::ACL::RunTask request; if (framework->info.has_principal()) { request.mutable_principals()->add_values(framework->info.principal()); } else { http://git-wip-us.apache.org/repos/asf/mesos/blob/a5cc9b43/src/master/master.hpp ---------------------------------------------------------------------- diff --git a/src/master/master.hpp b/src/master/master.hpp index 29e8f49..c9f989a 100644 --- a/src/master/master.hpp +++ b/src/master/master.hpp @@ -437,6 +437,16 @@ private: const static std::string TASKS_HELP; private: + // Helper for doing authentication, returns the credential used if + // the authentication was successful (or none if no credentials + // have been given to the master), otherwise an Error. + Result<Credential> authenticate(const process::http::Request& request); + + // Continuations. + process::Future<process::http::Response> _shutdown( + const FrameworkID& id, + bool authorized = true); + Master* master; } http; http://git-wip-us.apache.org/repos/asf/mesos/blob/a5cc9b43/src/sasl/authenticator.hpp ---------------------------------------------------------------------- diff --git a/src/sasl/authenticator.hpp b/src/sasl/authenticator.hpp index aa222d3..35ab794 100644 --- a/src/sasl/authenticator.hpp +++ b/src/sasl/authenticator.hpp @@ -466,11 +466,11 @@ void load(const std::map<std::string, std::string>& secrets) InMemoryAuxiliaryPropertyPlugin::load(properties); } -void load(const Credentials& c) +void load(const Credentials& credentials) { std::map<std::string, std::string> secrets; - foreach(const Credential& registration, c.registration()) { - secrets[registration.principal()] = registration.secret(); + foreach(const Credential& credential, credentials.credentials()) { + secrets[credential.principal()] = credential.secret(); } load(secrets); } http://git-wip-us.apache.org/repos/asf/mesos/blob/a5cc9b43/src/tests/authorization_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/authorization_tests.cpp b/src/tests/authorization_tests.cpp index 611f371..a8fa4cc 100644 --- a/src/tests/authorization_tests.cpp +++ b/src/tests/authorization_tests.cpp @@ -37,7 +37,7 @@ TEST_F(AuthorizationTest, AnyPrincipalRunAsUser) { // Any principal can run as "guest" user. ACLs acls; - mesos::ACL::RunTasks* acl = acls.add_run_tasks(); + mesos::ACL::RunTask* acl = acls.add_run_tasks(); acl->mutable_principals()->set_type(mesos::ACL::Entity::ANY); acl->mutable_users()->add_values("guest"); @@ -46,14 +46,14 @@ TEST_F(AuthorizationTest, AnyPrincipalRunAsUser) ASSERT_SOME(authorizer); // Principals "foo" and "bar" can run as "guest". - mesos::ACL::RunTasks request; + mesos::ACL::RunTask request; request.mutable_principals()->add_values("foo"); request.mutable_principals()->add_values("bar"); request.mutable_users()->add_values("guest"); AWAIT_EXPECT_EQ(true, authorizer.get()->authorize(request)); // Principal "foo" can run as "root" since the ACLs are permissive. - mesos::ACL::RunTasks request2; + mesos::ACL::RunTask request2; request2.mutable_principals()->add_values("foo"); request2.mutable_users()->add_values("root"); AWAIT_EXPECT_EQ(true, authorizer.get()->authorize(request2)); @@ -64,7 +64,7 @@ TEST_F(AuthorizationTest, NoPrincipalRunAsUser) { // No principal can run as "root" user. ACLs acls; - mesos::ACL::RunTasks* acl = acls.add_run_tasks(); + mesos::ACL::RunTask* acl = acls.add_run_tasks(); acl->mutable_principals()->set_type(mesos::ACL::Entity::NONE); acl->mutable_users()->add_values("root"); @@ -73,7 +73,7 @@ TEST_F(AuthorizationTest, NoPrincipalRunAsUser) ASSERT_SOME(authorizer); // Principal "foo" cannot run as "root". - mesos::ACL::RunTasks request; + mesos::ACL::RunTask request; request.mutable_principals()->add_values("foo"); request.mutable_users()->add_values("root"); AWAIT_EXPECT_EQ(false, authorizer.get()->authorize(request)); @@ -84,7 +84,7 @@ TEST_F(AuthorizationTest, PrincipalRunAsAnyUser) { // A principal "foo" can run as any user. ACLs acls; - mesos::ACL::RunTasks* acl = acls.add_run_tasks(); + mesos::ACL::RunTask* acl = acls.add_run_tasks(); acl->mutable_principals()->add_values("foo"); acl->mutable_users()->set_type(mesos::ACL::Entity::ANY); @@ -93,7 +93,7 @@ TEST_F(AuthorizationTest, PrincipalRunAsAnyUser) ASSERT_SOME(authorizer); // Principal "foo" can run as "user1" and "user2". - mesos::ACL::RunTasks request; + mesos::ACL::RunTask request; request.mutable_principals()->add_values("foo"); request.mutable_users()->add_values("user1"); request.mutable_users()->add_values("user2"); @@ -105,7 +105,7 @@ TEST_F(AuthorizationTest, AnyPrincipalRunAsAnyUser) { // Any principal can run as any user. ACLs acls; - mesos::ACL::RunTasks* acl = acls.add_run_tasks(); + mesos::ACL::RunTask* acl = acls.add_run_tasks(); acl->mutable_principals()->set_type(mesos::ACL::Entity::ANY); acl->mutable_users()->set_type(mesos::ACL::Entity::ANY); @@ -114,7 +114,7 @@ TEST_F(AuthorizationTest, AnyPrincipalRunAsAnyUser) ASSERT_SOME(authorizer); // Principals "foo" and "bar" can run as "user1" and "user2". - mesos::ACL::RunTasks request; + mesos::ACL::RunTask request; request.mutable_principals()->add_values("foo"); request.mutable_principals()->add_values("bar"); request.mutable_users()->add_values("user1"); @@ -129,14 +129,14 @@ TEST_F(AuthorizationTest, OnlySomePrincipalsRunAsSomeUsers) ACLs acls; // ACL for some principals to run as some users. - mesos::ACL::RunTasks* acl = acls.add_run_tasks(); + mesos::ACL::RunTask* acl = acls.add_run_tasks(); acl->mutable_principals()->add_values("foo"); acl->mutable_principals()->add_values("bar"); acl->mutable_users()->add_values("user1"); acl->mutable_users()->add_values("user2"); // ACL for no one else to run as some users. - mesos::ACL::RunTasks* acl2 = acls.add_run_tasks(); + mesos::ACL::RunTask* acl2 = acls.add_run_tasks(); acl2->mutable_principals()->set_type(mesos::ACL::Entity::NONE); acl2->mutable_users()->add_values("user1"); acl2->mutable_users()->add_values("user2"); @@ -146,7 +146,7 @@ TEST_F(AuthorizationTest, OnlySomePrincipalsRunAsSomeUsers) ASSERT_SOME(authorizer); // Principals "foo" and "bar" can run as "user1" and "user2". - mesos::ACL::RunTasks request; + mesos::ACL::RunTask request; request.mutable_principals()->add_values("foo"); request.mutable_principals()->add_values("bar"); request.mutable_users()->add_values("user1"); @@ -154,13 +154,13 @@ TEST_F(AuthorizationTest, OnlySomePrincipalsRunAsSomeUsers) AWAIT_EXPECT_EQ(true, authorizer.get()->authorize(request)); // Principal "baz" cannot run as "user1". - mesos::ACL::RunTasks request2; + mesos::ACL::RunTask request2; request2.mutable_principals()->add_values("baz"); request2.mutable_users()->add_values("user1"); AWAIT_EXPECT_EQ(false, authorizer.get()->authorize(request2)); // Principal "baz" cannot run as "user2". - mesos::ACL::RunTasks request3; + mesos::ACL::RunTask request3; request3.mutable_principals()->add_values("baz"); request3.mutable_users()->add_values("user1"); AWAIT_EXPECT_EQ(false, authorizer.get()->authorize(request3)); @@ -173,12 +173,12 @@ TEST_F(AuthorizationTest, SomePrincipalOnlySomeUser) ACLs acls; // ACL for some principal to run as some user. - mesos::ACL::RunTasks* acl = acls.add_run_tasks(); + mesos::ACL::RunTask* acl = acls.add_run_tasks(); acl->mutable_principals()->add_values("foo"); acl->mutable_users()->add_values("user1"); // ACL for some principal to not run as any other user. - mesos::ACL::RunTasks* acl2 = acls.add_run_tasks(); + mesos::ACL::RunTask* acl2 = acls.add_run_tasks(); acl2->mutable_principals()->add_values("foo"); acl2->mutable_users()->set_type(mesos::ACL::Entity::NONE); @@ -187,19 +187,19 @@ TEST_F(AuthorizationTest, SomePrincipalOnlySomeUser) ASSERT_SOME(authorizer); // Principal "foo" can run as "user1". - mesos::ACL::RunTasks request; + mesos::ACL::RunTask request; request.mutable_principals()->add_values("foo"); request.mutable_users()->add_values("user1"); AWAIT_EXPECT_EQ(true, authorizer.get()->authorize(request)); // Principal "foo" cannot run as "user2". - mesos::ACL::RunTasks request2; + mesos::ACL::RunTask request2; request2.mutable_principals()->add_values("foo"); request2.mutable_users()->add_values("user2"); AWAIT_EXPECT_EQ(false, authorizer.get()->authorize(request2)); // Principal "bar" can run as "user1" and "user2". - mesos::ACL::RunTasks request3; + mesos::ACL::RunTask request3; request3.mutable_principals()->add_values("bar"); request3.mutable_users()->add_values("user1"); request3.mutable_users()->add_values("user2"); @@ -212,7 +212,7 @@ TEST_F(AuthorizationTest, PrincipalRunAsSomeUserRestrictive) // A principal can run as "user1"; ACLs acls; acls.set_permissive(false); // Restrictive. - mesos::ACL::RunTasks* acl = acls.add_run_tasks(); + mesos::ACL::RunTask* acl = acls.add_run_tasks(); acl->mutable_principals()->add_values("foo"); acl->mutable_users()->add_values("user1"); @@ -221,19 +221,19 @@ TEST_F(AuthorizationTest, PrincipalRunAsSomeUserRestrictive) ASSERT_SOME(authorizer); // Principal "foo" can run as "user1". - mesos::ACL::RunTasks request; + mesos::ACL::RunTask request; request.mutable_principals()->add_values("foo"); request.mutable_users()->add_values("user1"); AWAIT_EXPECT_EQ(true, authorizer.get()->authorize(request)); // Principal "foo" cannot run as "user2". - mesos::ACL::RunTasks request2; + mesos::ACL::RunTask request2; request2.mutable_principals()->add_values("foo"); request2.mutable_users()->add_values("user2"); AWAIT_EXPECT_EQ(false, authorizer.get()->authorize(request2)); // Principal "bar" cannot run as "user2" since no ACL is set. - mesos::ACL::RunTasks request3; + mesos::ACL::RunTask request3; request3.mutable_principals()->add_values("bar"); request3.mutable_users()->add_values("user2"); AWAIT_EXPECT_EQ(false, authorizer.get()->authorize(request3)); @@ -244,7 +244,7 @@ TEST_F(AuthorizationTest, AnyPrincipalOfferedRole) { // Any principal can be offered "*" role's resources. ACLs acls; - mesos::ACL::ReceiveOffers* acl = acls.add_receive_offers(); + mesos::ACL::RegisterFramework* acl = acls.add_register_frameworks(); acl->mutable_principals()->set_type(mesos::ACL::Entity::ANY); acl->mutable_roles()->add_values("*"); @@ -253,7 +253,7 @@ TEST_F(AuthorizationTest, AnyPrincipalOfferedRole) ASSERT_SOME(authorizer); // Principals "foo" and "bar" can be offered "*" role's resources. - mesos::ACL::ReceiveOffers request; + mesos::ACL::RegisterFramework request; request.mutable_principals()->add_values("foo"); request.mutable_principals()->add_values("bar"); request.mutable_roles()->add_values("*"); @@ -265,7 +265,7 @@ TEST_F(AuthorizationTest, SomePrincipalsOfferedRole) { // Some principals can be offered "ads" role's resources. ACLs acls; - mesos::ACL::ReceiveOffers* acl = acls.add_receive_offers(); + mesos::ACL::RegisterFramework* acl = acls.add_register_frameworks(); acl->mutable_principals()->add_values("foo"); acl->mutable_principals()->add_values("bar"); acl->mutable_roles()->add_values("ads"); @@ -276,7 +276,7 @@ TEST_F(AuthorizationTest, SomePrincipalsOfferedRole) // Principals "foo", "bar" and "baz" (no ACL) can be offered "ads" // role's resources. - mesos::ACL::ReceiveOffers request; + mesos::ACL::RegisterFramework request; request.mutable_principals()->add_values("foo"); request.mutable_principals()->add_values("bar"); request.mutable_principals()->add_values("baz"); @@ -291,12 +291,12 @@ TEST_F(AuthorizationTest, PrincipalOfferedRole) ACLs acls; // ACL for a principal to be offered "analytics" role's resources. - mesos::ACL::ReceiveOffers* acl = acls.add_receive_offers(); + mesos::ACL::RegisterFramework* acl = acls.add_register_frameworks(); acl->mutable_principals()->add_values("foo"); acl->mutable_roles()->add_values("analytics"); // ACL for no one else to be offered "analytics" role's resources. - mesos::ACL::ReceiveOffers* acl2 = acls.add_receive_offers(); + mesos::ACL::RegisterFramework* acl2 = acls.add_register_frameworks(); acl2->mutable_principals()->set_type(mesos::ACL::Entity::NONE); acl2->mutable_roles()->add_values("analytics"); @@ -305,13 +305,13 @@ TEST_F(AuthorizationTest, PrincipalOfferedRole) ASSERT_SOME(authorizer); // Principal "foo" can be offered "analytics" role's resources. - mesos::ACL::ReceiveOffers request; + mesos::ACL::RegisterFramework request; request.mutable_principals()->add_values("foo"); request.mutable_roles()->add_values("analytics"); AWAIT_EXPECT_EQ(true, authorizer.get()->authorize(request)); // Principal "bar" cannot be offered "analytics" role's resources. - mesos::ACL::ReceiveOffers request2; + mesos::ACL::RegisterFramework request2; request2.mutable_principals()->add_values("bar"); request2.mutable_roles()->add_values("analytics"); AWAIT_EXPECT_EQ(false, authorizer.get()->authorize(request2)); @@ -323,7 +323,7 @@ TEST_F(AuthorizationTest, PrincipalNotOfferedAnyRoleRestrictive) // A principal "foo" can be offered "analytics" role's resources. ACLs acls; acls.set_permissive(false); - mesos::ACL::ReceiveOffers* acl = acls.add_receive_offers(); + mesos::ACL::RegisterFramework* acl = acls.add_register_frameworks(); acl->mutable_principals()->add_values("foo"); acl->mutable_roles()->add_values("analytics"); @@ -332,210 +332,20 @@ TEST_F(AuthorizationTest, PrincipalNotOfferedAnyRoleRestrictive) ASSERT_SOME(authorizer); // Principal "foo" can be offered "analytics" role's resources. - mesos::ACL::ReceiveOffers request; + mesos::ACL::RegisterFramework request; request.mutable_principals()->add_values("foo"); request.mutable_roles()->add_values("analytics"); AWAIT_EXPECT_EQ(true, authorizer.get()->authorize(request)); // Principal "bar" cannot be offered "analytics" role's resources. - mesos::ACL::ReceiveOffers request2; + mesos::ACL::RegisterFramework request2; request2.mutable_principals()->add_values("bar"); request2.mutable_roles()->add_values("analytics"); AWAIT_EXPECT_EQ(false, authorizer.get()->authorize(request2)); // Principal "bar" cannot be offered "ads" role's resources because no ACL. - mesos::ACL::ReceiveOffers request3; + mesos::ACL::RegisterFramework request3; request3.mutable_principals()->add_values("bar"); request3.mutable_roles()->add_values("ads"); AWAIT_EXPECT_EQ(false, authorizer.get()->authorize(request3)); } - - -TEST_F(AuthorizationTest, AnyClientGETSomeURL) -{ - // Any client can GET access "/help". - ACLs acls; - mesos::ACL::HTTPGet* acl = acls.add_http_get(); - acl->mutable_usernames()->set_type(mesos::ACL::Entity::ANY); - acl->mutable_ips()->set_type(mesos::ACL::Entity::ANY); - acl->mutable_hostnames()->set_type(mesos::ACL::Entity::ANY); - acl->mutable_urls()->add_values("/help"); - - // Create an Authorizer with the ACLs. - Try<Owned<LocalAuthorizer> > authorizer = LocalAuthorizer::create(acls); - ASSERT_SOME(authorizer); - - // Clients "foo", "127.0.0.1", "localhost" can GET access "/help". - mesos::ACL::HTTPGet request; - request.mutable_usernames()->add_values("foo"); - request.mutable_ips()->add_values("127.0.0.1"); - request.mutable_hostnames()->add_values("localhost"); - request.mutable_urls()->add_values("/help"); - AWAIT_EXPECT_EQ(true, authorizer.get()->authorize(request)); -} - - -TEST_F(AuthorizationTest, SomeClientsPUTSomeURL) -{ - // Only some clients can PUT access "/admin". - ACLs acls; - - // Some clients can PUT access "/admin". - mesos::ACL::HTTPPut* acl = acls.add_http_put(); - acl->mutable_ips()->add_values("127.0.0.1"); - acl->mutable_hostnames()->add_values("localhost"); - acl->mutable_urls()->add_values("/admin"); - - // No one else can PUT access "/admin". - mesos::ACL::HTTPPut* acl2 = acls.add_http_put(); - acl2->mutable_usernames()->set_type(mesos::ACL::Entity::NONE); - acl2->mutable_ips()->set_type(mesos::ACL::Entity::NONE); - acl2->mutable_hostnames()->set_type(mesos::ACL::Entity::NONE); - acl2->mutable_urls()->add_values("/admin"); - - // Create an Authorizer with the ACLs. - Try<Owned<LocalAuthorizer> > authorizer = LocalAuthorizer::create(acls); - ASSERT_SOME(authorizer); - - // Clients "127.0.0.1" and "localhost" can PUT access "/admin". - mesos::ACL::HTTPPut request; - request.mutable_ips()->add_values("127.0.0.1"); - request.mutable_hostnames()->add_values("localhost"); - request.mutable_urls()->add_values("/admin"); - AWAIT_EXPECT_EQ(true, authorizer.get()->authorize(request)); - - // Client "10.0.0.0" cannot PUT access "/admin". - mesos::ACL::HTTPPut request2; - request2.mutable_ips()->add_values("10.0.0.0"); - request2.mutable_urls()->add_values("/admin"); - AWAIT_EXPECT_EQ(false, authorizer.get()->authorize(request2)); -} - - -TEST_F(AuthorizationTest, NoClientGETPUTSomeURL) -{ - // No client can GET access "/secret". - ACLs acls; - mesos::ACL::HTTPGet* acl = acls.add_http_get(); - acl->mutable_usernames()->set_type(mesos::ACL::Entity::NONE); - acl->mutable_ips()->set_type(mesos::ACL::Entity::NONE); - acl->mutable_hostnames()->set_type(mesos::ACL::Entity::NONE); - acl->mutable_urls()->add_values("/secret"); - - // No client can PUT access "/secret". - mesos::ACL::HTTPPut* acl2 = acls.add_http_put(); - acl2->mutable_usernames()->set_type(mesos::ACL::Entity::NONE); - acl2->mutable_ips()->set_type(mesos::ACL::Entity::NONE); - acl2->mutable_hostnames()->set_type(mesos::ACL::Entity::NONE); - acl2->mutable_urls()->add_values("/secret"); - - // Create an Authorizer with the ACLs. - Try<Owned<LocalAuthorizer> > authorizer = LocalAuthorizer::create(acls); - ASSERT_SOME(authorizer); - - // Clients "127.0.0.1" and "localhost" cannot GET access "/secret". - mesos::ACL::HTTPGet request; - request.mutable_ips()->add_values("127.0.0.1"); - request.mutable_hostnames()->add_values("localhost"); - request.mutable_urls()->add_values("/secret"); - AWAIT_EXPECT_EQ(false, authorizer.get()->authorize(request)); - - // Clients "127.0.0.1" and "localhost" cannot PUT access "/secret". - mesos::ACL::HTTPPut request2; - request2.mutable_ips()->add_values("127.0.0.1"); - request2.mutable_hostnames()->add_values("localhost"); - request2.mutable_urls()->add_values("/secret"); - AWAIT_EXPECT_EQ(false, authorizer.get()->authorize(request2)); -} - - -TEST_F(AuthorizationTest, SomeClientsCannotGETAnyURL) -{ - // Some clients cannot GET access any URL. - ACLs acls; - mesos::ACL::HTTPGet* acl = acls.add_http_get(); - acl->mutable_ips()->add_values("127.0.0.1"); - acl->mutable_hostnames()->add_values("localhost"); - acl->mutable_urls()->set_type(mesos::ACL::Entity::NONE); - - // Create an Authorizer with the ACLs. - Try<Owned<LocalAuthorizer> > authorizer = LocalAuthorizer::create(acls); - ASSERT_SOME(authorizer); - - // Clients "127.0.0.1" and "localhost" cannot GET access "/help". - mesos::ACL::HTTPGet request; - request.mutable_ips()->add_values("127.0.0.1"); - request.mutable_hostnames()->add_values("localhost"); - request.mutable_urls()->add_values("/help"); - AWAIT_EXPECT_EQ(false, authorizer.get()->authorize(request)); -} - - -TEST_F(AuthorizationTest, NoClientsCanGETPUTAnyURLRestrictive) -{ - // No clients can GET/PUT access any URL. - ACLs acls; - acls.set_permissive(false); - - // Create an Authorizer with the ACLs. - Try<Owned<LocalAuthorizer> > authorizer = LocalAuthorizer::create(acls); - ASSERT_SOME(authorizer); - - // Clients "foo", "127.0.0.1" cannot GET access "/help". - mesos::ACL::HTTPGet request; - request.mutable_usernames()->add_values("foo"); - request.mutable_ips()->add_values("127.0.0.1"); - request.mutable_urls()->add_values("/help"); - AWAIT_EXPECT_EQ(false, authorizer.get()->authorize(request)); - - // Clients "127.0.0.1", "localhost" cannot PUT access "/help". - mesos::ACL::HTTPPut request2; - request2.mutable_ips()->add_values("127.0.0.1"); - request2.mutable_hostnames()->add_values("localhost"); - request2.mutable_urls()->add_values("/help"); - AWAIT_EXPECT_EQ(false, authorizer.get()->authorize(request2)); -} - - -TEST_F(AuthorizationTest, SomeClientsAggregatePUTRequestRestrictive) -{ - // Some clients can PUT access "/admin" but ACLs are setup - // separately. - ACLs acls; - acls.set_permissive(false); - - // "foo" can PUT access "/admin". - mesos::ACL::HTTPPut* acl = acls.add_http_put(); - acl->mutable_usernames()->add_values("foo"); - acl->mutable_urls()->add_values("/admin"); - - // "bar" can PUT access "/admin". - mesos::ACL::HTTPPut* acl2 = acls.add_http_put(); - acl2->mutable_usernames()->add_values("bar"); - acl2->mutable_urls()->add_values("/admin"); - - // Create an Authorizer with the ACLs. - Try<Owned<LocalAuthorizer> > authorizer = LocalAuthorizer::create(acls); - ASSERT_SOME(authorizer); - - // "foo" can PUT access "/admin". - mesos::ACL::HTTPPut request; - request.mutable_usernames()->add_values("foo"); - request.mutable_urls()->add_values("/admin"); - AWAIT_EXPECT_EQ(true, authorizer.get()->authorize(request)); - - // "bar" can PUT access "/admin". - mesos::ACL::HTTPPut request2; - request2.mutable_usernames()->add_values("bar"); - request2.mutable_urls()->add_values("/admin"); - AWAIT_EXPECT_EQ(true, authorizer.get()->authorize(request2)); - - // Aggregate request for clients "foo" and "bar" for PUT access to - // "/admin" is not allowed because ACLs are not aggregated. - mesos::ACL::HTTPPut request3; - request3.mutable_usernames()->add_values("foo"); - request3.mutable_usernames()->add_values("bar"); - request3.mutable_urls()->add_values("/admin"); - AWAIT_EXPECT_EQ(false, authorizer.get()->authorize(request3)); -} - http://git-wip-us.apache.org/repos/asf/mesos/blob/a5cc9b43/src/tests/master_authorization_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/master_authorization_tests.cpp b/src/tests/master_authorization_tests.cpp index 5c35577..f0f0648 100644 --- a/src/tests/master_authorization_tests.cpp +++ b/src/tests/master_authorization_tests.cpp @@ -72,7 +72,7 @@ TEST_F(MasterAuthorizationTest, AuthorizedTask) { // Setup ACLs so that the framework can launch tasks as "foo". ACLs acls; - mesos::ACL::RunTasks* acl = acls.add_run_tasks(); + mesos::ACL::RunTask* acl = acls.add_run_tasks(); acl->mutable_principals()->add_values(DEFAULT_FRAMEWORK_INFO.principal()); acl->mutable_users()->add_values("foo"); @@ -150,7 +150,7 @@ TEST_F(MasterAuthorizationTest, UnauthorizedTask) { // Setup ACLs so that no framework can launch as "foo". ACLs acls; - mesos::ACL::RunTasks* acl = acls.add_run_tasks(); + mesos::ACL::RunTask* acl = acls.add_run_tasks(); acl->mutable_principals()->set_type(mesos::ACL::Entity::NONE); acl->mutable_users()->add_values("foo"); @@ -251,7 +251,7 @@ TEST_F(MasterAuthorizationTest, KillTask) // Return a pending future from authorizer. Future<Nothing> future; Promise<bool> promise; - EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::RunTasks&>())) + EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::RunTask&>())) .WillOnce(DoAll(FutureSatisfy(&future), Return(promise.future()))); @@ -325,7 +325,7 @@ TEST_F(MasterAuthorizationTest, SlaveRemoved) // Return a pending future from authorizer. Future<Nothing> future; Promise<bool> promise; - EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::RunTasks&>())) + EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::RunTask&>())) .WillOnce(DoAll(FutureSatisfy(&future), Return(promise.future()))); @@ -409,7 +409,7 @@ TEST_F(MasterAuthorizationTest, SlaveDisconnected) // Return a pending future from authorizer. Future<Nothing> future; Promise<bool> promise; - EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::RunTasks&>())) + EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::RunTask&>())) .WillOnce(DoAll(FutureSatisfy(&future), Return(promise.future()))); @@ -491,7 +491,7 @@ TEST_F(MasterAuthorizationTest, FrameworkRemoved) // Return a pending future from authorizer. Future<Nothing> future; Promise<bool> promise; - EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::RunTasks&>())) + EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::RunTask&>())) .WillOnce(DoAll(FutureSatisfy(&future), Return(promise.future()))); @@ -560,7 +560,7 @@ TEST_F(MasterAuthorizationTest, ReconcileTask) // Return a pending future from authorizer. Future<Nothing> future; Promise<bool> promise; - EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::RunTasks&>())) + EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::RunTask&>())) .WillOnce(DoAll(FutureSatisfy(&future), Return(promise.future()))); @@ -649,7 +649,7 @@ TEST_F(MasterAuthorizationTest, PendingExecutorInfoDiffersOnDifferentSlaves) // Return a pending future from authorizer. Future<Nothing> future; Promise<bool> promise; - EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::RunTasks&>())) + EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::RunTask&>())) .WillOnce(DoAll(FutureSatisfy(&future), Return(promise.future()))); @@ -694,7 +694,7 @@ TEST_F(MasterAuthorizationTest, PendingExecutorInfoDiffersOnDifferentSlaves) EXPECT_CALL(sched, statusUpdate(&driver, _)) .WillOnce(FutureArg<1>(&status2)); - EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::RunTasks&>())) + EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::RunTask&>())) .WillOnce(Return(true)); driver.launchTasks(offers2.get()[0].id(), tasks2); @@ -738,7 +738,7 @@ TEST_F(MasterAuthorizationTest, AuthorizedRole) // Setup ACLs so that the framework can receive offers for role // "foo". ACLs acls; - mesos::ACL::ReceiveOffers* acl = acls.add_receive_offers(); + mesos::ACL::RegisterFramework* acl = acls.add_register_frameworks(); acl->mutable_principals()->add_values(DEFAULT_FRAMEWORK_INFO.principal()); acl->mutable_roles()->add_values("foo"); @@ -779,7 +779,7 @@ TEST_F(MasterAuthorizationTest, UnauthorizedRole) // Setup ACLs so that no framework can receive offers for role // "foo". ACLs acls; - mesos::ACL::ReceiveOffers* acl = acls.add_receive_offers(); + mesos::ACL::RegisterFramework* acl = acls.add_register_frameworks(); acl->mutable_principals()->set_type(mesos::ACL::Entity::NONE); acl->mutable_roles()->add_values("foo"); @@ -840,7 +840,7 @@ TEST_F(MasterAuthorizationTest, DuplicateRegistration) Promise<bool> promise1; Future<Nothing> future2; Promise<bool> promise2; - EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::ReceiveOffers&>())) + EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::RegisterFramework&>())) .WillOnce(DoAll(FutureSatisfy(&future1), Return(promise1.future()))) .WillOnce(DoAll(FutureSatisfy(&future2), @@ -906,7 +906,7 @@ TEST_F(MasterAuthorizationTest, DuplicateReregistration) Promise<bool> promise2; Future<Nothing> future3; Promise<bool> promise3; - EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::ReceiveOffers&>())) + EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::RegisterFramework&>())) .WillOnce(Return(true)) .WillOnce(DoAll(FutureSatisfy(&future2), Return(promise2.future()))) @@ -974,7 +974,7 @@ TEST_F(MasterAuthorizationTest, FrameworkRemovedBeforeRegistration) // Return a pending future from authorizer. Future<Nothing> future; Promise<bool> promise; - EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::ReceiveOffers&>())) + EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::RegisterFramework&>())) .WillOnce(DoAll(FutureSatisfy(&future), Return(promise.future()))); @@ -1032,7 +1032,7 @@ TEST_F(MasterAuthorizationTest, FrameworkRemovedBeforeReregistration) // Return a pending future from authorizer after first attempt. Future<Nothing> future2; Promise<bool> promise2; - EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::ReceiveOffers&>())) + EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::RegisterFramework&>())) .WillOnce(Return(true)) .WillOnce(DoAll(FutureSatisfy(&future2), Return(promise2.future()))); http://git-wip-us.apache.org/repos/asf/mesos/blob/a5cc9b43/src/tests/mesos.cpp ---------------------------------------------------------------------- diff --git a/src/tests/mesos.cpp b/src/tests/mesos.cpp index 6b5c43f..5bd8ba0 100644 --- a/src/tests/mesos.cpp +++ b/src/tests/mesos.cpp @@ -101,10 +101,7 @@ master::Flags MesosTest::CreateMasterFlags() // JSON default format for credentials Credentials credentials; - Credential* credential = credentials.add_registration(); - credential->set_principal(DEFAULT_CREDENTIAL.principal()); - credential->set_secret(DEFAULT_CREDENTIAL.secret()); - credential = credentials.add_http(); + Credential* credential = credentials.add_credentials(); credential->set_principal(DEFAULT_CREDENTIAL.principal()); credential->set_secret(DEFAULT_CREDENTIAL.secret()); http://git-wip-us.apache.org/repos/asf/mesos/blob/a5cc9b43/src/tests/mesos.hpp ---------------------------------------------------------------------- diff --git a/src/tests/mesos.hpp b/src/tests/mesos.hpp index 8cf71d1..b31c347 100644 --- a/src/tests/mesos.hpp +++ b/src/tests/mesos.hpp @@ -492,27 +492,22 @@ public: // NOTE: We use 'EXPECT_CALL' and 'WillRepeatedly' here instead of // 'ON_CALL' and 'WillByDefault'. See 'TestContainerizer::SetUp()' // for more details. - EXPECT_CALL(*this, authorize(An<const mesos::ACL::RunTasks&>())) + EXPECT_CALL(*this, authorize(An<const mesos::ACL::RegisterFramework&>())) .WillRepeatedly(Return(true)); - EXPECT_CALL(*this, authorize(An<const mesos::ACL::ReceiveOffers&>())) + EXPECT_CALL(*this, authorize(An<const mesos::ACL::RunTask&>())) .WillRepeatedly(Return(true)); - EXPECT_CALL(*this, authorize(An<const mesos::ACL::HTTPGet&>())) - .WillRepeatedly(Return(true)); - - EXPECT_CALL(*this, authorize(An<const mesos::ACL::HTTPPut&>())) + EXPECT_CALL(*this, authorize(An<const mesos::ACL::ShutdownFramework&>())) .WillRepeatedly(Return(true)); } MOCK_METHOD1( - authorize, process::Future<bool>(const ACL::RunTasks& request)); - MOCK_METHOD1( - authorize, process::Future<bool>(const ACL::ReceiveOffers& request)); + authorize, process::Future<bool>(const ACL::RegisterFramework& request)); MOCK_METHOD1( - authorize, process::Future<bool>(const ACL::HTTPGet& request)); + authorize, process::Future<bool>(const ACL::RunTask& request)); MOCK_METHOD1( - authorize, process::Future<bool>(const ACL::HTTPPut& request)); + authorize, process::Future<bool>(const ACL::ShutdownFramework& request)); }; http://git-wip-us.apache.org/repos/asf/mesos/blob/a5cc9b43/src/tests/reconciliation_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/reconciliation_tests.cpp b/src/tests/reconciliation_tests.cpp index 952f29b..3c4d7ed 100644 --- a/src/tests/reconciliation_tests.cpp +++ b/src/tests/reconciliation_tests.cpp @@ -622,7 +622,7 @@ TEST_F(ReconciliationTest, PendingTask) // Return a pending future from authorizer. Future<Nothing> future; Promise<bool> promise; - EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::RunTasks&>())) + EXPECT_CALL(authorizer, authorize(An<const mesos::ACL::RunTask&>())) .WillOnce(DoAll(FutureSatisfy(&future), Return(promise.future()))); http://git-wip-us.apache.org/repos/asf/mesos/blob/a5cc9b43/src/tests/script.cpp ---------------------------------------------------------------------- diff --git a/src/tests/script.cpp b/src/tests/script.cpp index 3129479..515e314 100644 --- a/src/tests/script.cpp +++ b/src/tests/script.cpp @@ -135,16 +135,16 @@ void execute(const string& script) ACLs acls; acls.set_permissive(false); - mesos::ACL::RunTasks* run = acls.add_run_tasks(); + mesos::ACL::RunTask* run = acls.add_run_tasks(); run->mutable_principals()->add_values(DEFAULT_CREDENTIAL.principal()); Result<string> user = os::user(); CHECK_SOME(user) << "Failed to get current user name"; run->mutable_users()->add_values(user.get()); - mesos::ACL::ReceiveOffers* offer = acls.add_receive_offers(); - offer->mutable_principals()->add_values(DEFAULT_CREDENTIAL.principal()); - offer->mutable_roles()->add_values("*"); + mesos::ACL::RegisterFramework* register_ = acls.add_register_frameworks(); + register_->mutable_principals()->add_values(DEFAULT_CREDENTIAL.principal()); + register_->mutable_roles()->add_values("*"); const string& aclsPath = path::join(directory.get(), "acls"); http://git-wip-us.apache.org/repos/asf/mesos/blob/a5cc9b43/src/tests/shutdown_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/shutdown_tests.cpp b/src/tests/shutdown_tests.cpp index ad13aa1..12ebef4 100644 --- a/src/tests/shutdown_tests.cpp +++ b/src/tests/shutdown_tests.cpp @@ -64,9 +64,9 @@ using testing::Return; class ShutdownTest : public MesosTest {}; // Testing /master/shutdown so this endopoint shuts down -// designated framework or return adequate error +// designated framework or return adequate error. -// Testing route with authorization header and good credentials +// Testing route with authorization header and good credentials. TEST_F(ShutdownTest, ShutdownEndpoint) { Try<PID<Master> > master = StartMaster(); @@ -105,7 +105,7 @@ TEST_F(ShutdownTest, ShutdownEndpoint) } -// Testing route with bad credentials +// Testing route with bad credentials. TEST_F(ShutdownTest, ShutdownEndpointBadCredentials) { Try<PID<Master> > master = StartMaster(); @@ -145,7 +145,106 @@ TEST_F(ShutdownTest, ShutdownEndpointBadCredentials) } -// Testing route without frameworkId value +// Testing route with good ACLs. +TEST_F(ShutdownTest, ShutdownEndpointGoodACLs) +{ + // Setup ACLs so that the default principal can shutdown the + // framework. + ACLs acls; + mesos::ACL::ShutdownFramework* acl = acls.add_shutdown_frameworks(); + acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL.principal()); + acl->mutable_framework_principals()->add_values( + DEFAULT_CREDENTIAL.principal()); + + master::Flags flags = CreateMasterFlags(); + flags.acls = acls; + Try<PID<Master> > master = StartMaster(flags); + ASSERT_SOME(master); + + MockScheduler sched; + MesosSchedulerDriver driver( + &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL); + + Future<FrameworkID> frameworkId; + EXPECT_CALL(sched, registered(&driver, _, _)) + .WillOnce(FutureArg<1>(&frameworkId)); + + ASSERT_EQ(DRIVER_RUNNING, driver.start()); + + AWAIT_READY(frameworkId); + + hashmap<string, string> headers; + headers["Authorization"] = "Basic " + + base64::encode(DEFAULT_CREDENTIAL.principal() + + ":" + DEFAULT_CREDENTIAL.secret()); + + Future<Response> response = process::http::post( + master.get(), + "shutdown", + headers, + "frameworkId=" + frameworkId.get().value()); + + AWAIT_READY(response); + AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); + + driver.stop(); + driver.join(); + + Shutdown(); +} + + +// Testing route with bad ACLs. +TEST_F(ShutdownTest, ShutdownEndpointBadACLs) +{ + // Setup ACLs so that no principal can do shutdown the framework. + ACLs acls; + mesos::ACL::ShutdownFramework* acl = acls.add_shutdown_frameworks(); + acl->mutable_principals()->set_type(mesos::ACL::Entity::NONE); + acl->mutable_framework_principals()->add_values( + DEFAULT_CREDENTIAL.principal()); + + master::Flags flags = CreateMasterFlags(); + flags.acls = acls; + Try<PID<Master> > master = StartMaster(flags); + ASSERT_SOME(master); + + MockScheduler sched; + MesosSchedulerDriver driver( + &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL); + + Future<FrameworkID> frameworkId; + EXPECT_CALL(sched, registered(&driver, _, _)) + .WillOnce(FutureArg<1>(&frameworkId)); + + ASSERT_EQ(DRIVER_RUNNING, driver.start()); + + AWAIT_READY(frameworkId); + + hashmap<string, string> headers; + headers["Authorization"] = "Basic " + + base64::encode(DEFAULT_CREDENTIAL.principal() + + ":" + DEFAULT_CREDENTIAL.secret()); + + Future<Response> response = process::http::post( + master.get(), + "shutdown", + headers, + "frameworkId=" + frameworkId.get().value()); + + AWAIT_READY(response); + AWAIT_EXPECT_RESPONSE_STATUS_EQ( + Unauthorized("Mesos master").status, + response); + + driver.stop(); + driver.join(); + + Shutdown(); +} + + +// Testing route without frameworkId value. TEST_F(ShutdownTest, ShutdownEndpointNoFrameworkId) { Try<PID<Master> > master = StartMaster(); @@ -177,7 +276,7 @@ TEST_F(ShutdownTest, ShutdownEndpointNoFrameworkId) } -// Testing route without authorization header +// Testing route without authorization header. TEST_F(ShutdownTest, ShutdownEndpointNoHeader) { Try<PID<Master> > master = StartMaster();
