Repository: mesos Updated Branches: refs/heads/1.0.x 6cd20e9f3 -> d15e684cb
Separated AuthN for readonly and readwrite endpoints in Mesos. Changes included: - separate flags for readonly and readwrite endpoints; - helper function for registering http authenticator; - fixing existing tests. Review: https://reviews.apache.org/r/50223/ Project: http://git-wip-us.apache.org/repos/asf/mesos/repo Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/67369c01 Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/67369c01 Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/67369c01 Branch: refs/heads/1.0.x Commit: 67369c01ce3eac2ff9aab4f776c0815cec3b883c Parents: a76800c Author: Zhitao Li <zhitaoli...@gmail.com> Authored: Thu Jul 21 23:43:34 2016 -0700 Committer: Adam B <a...@mesosphere.io> Committed: Fri Jul 22 02:02:24 2016 -0700 ---------------------------------------------------------------------- src/local/local.cpp | 2 +- src/master/constants.hpp | 9 +- src/master/flags.cpp | 20 +- src/master/flags.hpp | 3 +- src/master/main.cpp | 9 +- src/master/master.cpp | 275 ++++++++----------- src/master/master.hpp | 7 + src/slave/constants.hpp | 7 +- src/slave/flags.cpp | 17 +- src/slave/flags.hpp | 3 +- src/slave/main.cpp | 7 +- src/slave/slave.cpp | 209 +++++++------- src/slave/slave.hpp | 7 + src/tests/cluster.cpp | 11 +- src/tests/cluster.hpp | 4 +- src/tests/dynamic_weights_tests.cpp | 2 +- src/tests/logging_tests.cpp | 8 +- src/tests/main.cpp | 5 +- src/tests/master_quota_tests.cpp | 12 +- src/tests/master_tests.cpp | 58 +++- src/tests/mesos.cpp | 8 +- src/tests/mesos.hpp | 3 +- src/tests/metrics_tests.cpp | 16 +- src/tests/persistent_volume_endpoints_tests.cpp | 2 +- src/tests/reservation_endpoints_tests.cpp | 2 +- src/tests/slave_tests.cpp | 38 +++ 26 files changed, 417 insertions(+), 327 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mesos/blob/67369c01/src/local/local.cpp ---------------------------------------------------------------------- diff --git a/src/local/local.cpp b/src/local/local.cpp index a543aef..a1a9a6b 100644 --- a/src/local/local.cpp +++ b/src/local/local.cpp @@ -226,7 +226,7 @@ PID<Master> launch(const Flags& flags, Allocator* _allocator) state = new mesos::state::protobuf::State(storage); registrar = - new Registrar(flags, state, master::DEFAULT_HTTP_AUTHENTICATION_REALM); + new Registrar(flags, state, master::READONLY_HTTP_AUTHENTICATION_REALM); contender = new StandaloneMasterContender(); detector = new StandaloneMasterDetector(); http://git-wip-us.apache.org/repos/asf/mesos/blob/67369c01/src/master/constants.hpp ---------------------------------------------------------------------- diff --git a/src/master/constants.hpp b/src/master/constants.hpp index 410c388..e353b63 100644 --- a/src/master/constants.hpp +++ b/src/master/constants.hpp @@ -124,8 +124,13 @@ constexpr char DEFAULT_AUTHORIZER[] = "local"; // Name of the default, basic authenticator. constexpr char DEFAULT_HTTP_AUTHENTICATOR[] = "basic"; -// Name of the default master HTTP authentication realm. -constexpr char DEFAULT_HTTP_AUTHENTICATION_REALM[] = "mesos-master"; +// Name of the master HTTP authentication realm for read-only endpoints. +constexpr char READONLY_HTTP_AUTHENTICATION_REALM[] = + "mesos-master-readonly"; + +// Name of the master HTTP authentication realm for read-write endpoints. +constexpr char READWRITE_HTTP_AUTHENTICATION_REALM[] = + "mesos-master-readwrite"; // Name of the default authentication realm for HTTP frameworks. constexpr char DEFAULT_HTTP_FRAMEWORK_AUTHENTICATION_REALM[] = http://git-wip-us.apache.org/repos/asf/mesos/blob/67369c01/src/master/flags.cpp ---------------------------------------------------------------------- diff --git a/src/master/flags.cpp b/src/master/flags.cpp index ca3e80b..93ddf6b 100644 --- a/src/master/flags.cpp +++ b/src/master/flags.cpp @@ -222,11 +222,21 @@ mesos::internal::master::Flags::Flags() "If `false`, unauthenticated agents are also allowed to register.", false); - add(&Flags::authenticate_http, - "authenticate_http", - "If `true`, only authenticated requests for HTTP endpoints supporting\n" - "authentication are allowed. If `false`, unauthenticated requests to\n" - "HTTP endpoints are also allowed.\n", + // TODO(zhitao): Remove deprecated `--authenticate_http` flag name after + // the deprecation cycle which started with Mesos 1.0. + add(&Flags::authenticate_http_readwrite, + "authenticate_http_readwrite", + flags::DeprecatedName("authenticate_http"), + "If `true`, only authenticated requests for read-write HTTP endpoints\n" + "supporting authentication are allowed. If `false`, unauthenticated\n" + "requests to such HTTP endpoints are also allowed.", + false); + + add(&Flags::authenticate_http_readonly, + "authenticate_http_readonly", + "If `true`, only authenticated requests for read-only HTTP endpoints\n" + "supporting authentication are allowed. If `false`, unauthenticated\n" + "requests to such HTTP endpoints are also allowed.", false); add(&Flags::authenticate_http_frameworks, http://git-wip-us.apache.org/repos/asf/mesos/blob/67369c01/src/master/flags.hpp ---------------------------------------------------------------------- diff --git a/src/master/flags.hpp b/src/master/flags.hpp index a5dd11b..c6e8530 100644 --- a/src/master/flags.hpp +++ b/src/master/flags.hpp @@ -66,7 +66,8 @@ public: Option<std::string> weights; bool authenticate_frameworks; bool authenticate_agents; - bool authenticate_http; + bool authenticate_http_readonly; + bool authenticate_http_readwrite; bool authenticate_http_frameworks; Option<Path> credentials; Option<ACLs> acls; http://git-wip-us.apache.org/repos/asf/mesos/blob/67369c01/src/master/main.cpp ---------------------------------------------------------------------- diff --git a/src/master/main.cpp b/src/master/main.cpp index 9775b8a..6146c31 100644 --- a/src/master/main.cpp +++ b/src/master/main.cpp @@ -275,7 +275,10 @@ int main(int argc, char** argv) // `false`, then it has already been called, which means that the // authentication realm for libprocess-level HTTP endpoints was not set to the // correct value for the master. - if (!process::initialize("master", DEFAULT_HTTP_AUTHENTICATION_REALM)) { + if (!process::initialize( + "master", + READWRITE_HTTP_AUTHENTICATION_REALM, + READONLY_HTTP_AUTHENTICATION_REALM)) { EXIT(EXIT_FAILURE) << "The call to `process::initialize()` in the master's " << "`main()` was not the function's first invocation"; } @@ -435,7 +438,7 @@ int main(int argc, char** argv) mesos::state::protobuf::State* state = new mesos::state::protobuf::State(storage); Registrar* registrar = - new Registrar(flags, state, DEFAULT_HTTP_AUTHENTICATION_REALM); + new Registrar(flags, state, READONLY_HTTP_AUTHENTICATION_REALM); MasterContender* contender; MasterDetector* detector; @@ -504,7 +507,7 @@ int main(int argc, char** argv) createAuthorizationCallbacks(authorizer_.get())); } - Files files(DEFAULT_HTTP_AUTHENTICATION_REALM, authorizer_); + Files files(READONLY_HTTP_AUTHENTICATION_REALM, authorizer_); Option<shared_ptr<RateLimiter>> slaveRemovalLimiter = None(); if (flags.agent_removal_rate_limit.isSome()) { http://git-wip-us.apache.org/repos/asf/mesos/blob/67369c01/src/master/master.cpp ---------------------------------------------------------------------- diff --git a/src/master/master.cpp b/src/master/master.cpp index 79e3d78..219e850 100644 --- a/src/master/master.cpp +++ b/src/master/master.cpp @@ -377,6 +377,78 @@ struct BoundedRateLimiter }; +void Master::registerHttpAuthenticator( + const string& realm, + const Option<Credentials>& credentials, + const vector<string>& httpAuthenticatorNames) +{ + // At least the default authenticator is always specified. + // Passing an empty string into the `http_authenticators` + // flag is considered an error. + if (httpAuthenticatorNames.empty()) { + EXIT(EXIT_FAILURE) + << "No HTTP authenticator specified for realm '" << realm << "'"; + } + + if (httpAuthenticatorNames.size() > 1) { + EXIT(EXIT_FAILURE) << "Multiple HTTP authenticators not supported"; + } + + if (httpAuthenticatorNames[0] != DEFAULT_HTTP_AUTHENTICATOR && + !modules::ModuleManager::contains<authentication::Authenticator>( + httpAuthenticatorNames[0])) { + EXIT(EXIT_FAILURE) + << "HTTP authenticator '" << httpAuthenticatorNames[0] << "' not" + << " found. Check the spelling (compare to '" + << DEFAULT_HTTP_AUTHENTICATOR << "') or verify that the" + << " authenticator was loaded successfully (see --modules)"; + } + + Option<authentication::Authenticator*> httpAuthenticator; + if (httpAuthenticatorNames[0] == DEFAULT_HTTP_AUTHENTICATOR) { + if (credentials.isNone()) { + EXIT(EXIT_FAILURE) + << "No credentials provided for the default '" + << DEFAULT_HTTP_AUTHENTICATOR << "' HTTP authenticator for realm '" + << realm << "'"; + } + + LOG(INFO) << "Using default '" << DEFAULT_HTTP_AUTHENTICATOR + << "' HTTP authenticator for realm '" << realm << "'"; + + Try<authentication::Authenticator*> authenticator = + BasicAuthenticatorFactory::create(realm, credentials.get()); + if (authenticator.isError()) { + EXIT(EXIT_FAILURE) + << "Could not create HTTP authenticator module '" + << httpAuthenticatorNames[0] << "': " << authenticator.error(); + } + + httpAuthenticator = authenticator.get(); + } else { + Try<authentication::Authenticator*> module = + modules::ModuleManager::create<authentication::Authenticator>( + httpAuthenticatorNames[0]); + if (module.isError()) { + EXIT(EXIT_FAILURE) + << "Could not create HTTP authenticator module '" + << httpAuthenticatorNames[0] << "': " << module.error(); + } + LOG(INFO) << "Using '" << httpAuthenticatorNames[0] + << "' HTTP authenticator for realm '" << realm << "'"; + httpAuthenticator = module.get(); + } + + if (httpAuthenticator.isSome() && httpAuthenticator.get() != nullptr) { + // Ownership of the `httpAuthenticator` is passed to libprocess. + process::http::authentication::setAuthenticator( + realm, + Owned<authentication::Authenticator>(httpAuthenticator.get())); + httpAuthenticator = None(); + } +} + + void Master::initialize() { LOG(INFO) << "Master " << info_.id() << " (" << info_.hostname() << ")" @@ -541,75 +613,18 @@ void Master::initialize() } #endif // HAS_AUTHENTICATION - // TODO(arojas): Consider creating a factory function for the instantiation - // of the HTTP authenticator the same way the allocator does. - vector<string> httpAuthenticatorNames = - strings::split(flags.http_authenticators, ","); - - // At least the default authenticator is always specified. - // Passing an empty string into the `http_authenticators` - // flag is considered an error. - if (httpAuthenticatorNames.empty()) { - EXIT(EXIT_FAILURE) << "No HTTP authenticator specified"; - } - if (httpAuthenticatorNames.size() > 1) { - EXIT(EXIT_FAILURE) << "Multiple HTTP authenticators not supported"; - } - if (httpAuthenticatorNames[0] != DEFAULT_HTTP_AUTHENTICATOR && - !modules::ModuleManager::contains<authentication::Authenticator>( - httpAuthenticatorNames[0])) { - EXIT(EXIT_FAILURE) - << "HTTP authenticator '" << httpAuthenticatorNames[0] << "' not" - << " found. Check the spelling (compare to '" - << DEFAULT_HTTP_AUTHENTICATOR << "') or verify that the" - << " authenticator was loaded successfully (see --modules)"; - } - - Option<authentication::Authenticator*> httpAuthenticator; - - if (flags.authenticate_http) { - if (httpAuthenticatorNames[0] == DEFAULT_HTTP_AUTHENTICATOR) { - if (credentials.isNone()) { - EXIT(EXIT_FAILURE) - << "No credentials provided for the default '" - << DEFAULT_HTTP_AUTHENTICATOR << "' HTTP authenticator"; - } - - LOG(INFO) << "Using default '" << DEFAULT_HTTP_AUTHENTICATOR - << "' HTTP authenticator"; - - Try<authentication::Authenticator*> authenticator = - BasicAuthenticatorFactory::create( - DEFAULT_HTTP_AUTHENTICATION_REALM, - credentials.get()); - if (authenticator.isError()) { - EXIT(EXIT_FAILURE) - << "Could not create HTTP authenticator module '" - << httpAuthenticatorNames[0] << "': " << authenticator.error(); - } - - httpAuthenticator = authenticator.get(); - } else { - Try<authentication::Authenticator*> module = - modules::ModuleManager::create<authentication::Authenticator>( - httpAuthenticatorNames[0]); - if (module.isError()) { - EXIT(EXIT_FAILURE) - << "Could not create HTTP authenticator module '" - << httpAuthenticatorNames[0] << "': " << module.error(); - } - LOG(INFO) << "Using '" << httpAuthenticatorNames[0] - << "' HTTP authenticator"; - httpAuthenticator = module.get(); - } + if (flags.authenticate_http_readonly) { + registerHttpAuthenticator( + READONLY_HTTP_AUTHENTICATION_REALM, + credentials, + strings::split(flags.http_authenticators, ",")); } - if (httpAuthenticator.isSome() && httpAuthenticator.get() != nullptr) { - // Ownership of the `httpAuthenticator` is passed to libprocess. - process::http::authentication::setAuthenticator( - DEFAULT_HTTP_AUTHENTICATION_REALM, - Owned<authentication::Authenticator>(httpAuthenticator.get())); - httpAuthenticator = None(); + if (flags.authenticate_http_readwrite) { + registerHttpAuthenticator( + READWRITE_HTTP_AUTHENTICATION_REALM, + credentials, + strings::split(flags.http_authenticators, ",")); } if (flags.authenticate_http_frameworks) { @@ -621,84 +636,10 @@ void Master::initialize() << "in conjunction with `--authenticate_http_frameworks`"; } - vector<string> httpFrameworkAuthenticatorNames = - strings::split(flags.http_framework_authenticators.get(), ","); - - // Passing an empty string into the `http_framework_authenticators` - // flag is considered an error. - if (httpFrameworkAuthenticatorNames.empty()) { - EXIT(EXIT_FAILURE) << "No HTTP framework authenticator specified"; - } - - if (httpFrameworkAuthenticatorNames.size() > 1) { - EXIT(EXIT_FAILURE) << "Multiple HTTP framework authenticators not " - << "supported"; - } - - if (httpFrameworkAuthenticatorNames[0] != DEFAULT_HTTP_AUTHENTICATOR && - !modules::ModuleManager::contains<authentication::Authenticator>( - httpFrameworkAuthenticatorNames[0])) { - EXIT(EXIT_FAILURE) - << "HTTP framework authenticator '" - << httpFrameworkAuthenticatorNames[0] - << "' not found. Check the spelling (compare to '" - << DEFAULT_HTTP_AUTHENTICATOR << "') or verify that the" - << " authenticator was loaded successfully (see --modules)"; - } - - Option<authentication::Authenticator*> httpFrameworkAuthenticator; - - if (httpFrameworkAuthenticatorNames[0] == DEFAULT_HTTP_AUTHENTICATOR) { - if (credentials.isNone()) { - EXIT(EXIT_FAILURE) - << "No credentials provided for the default '" - << DEFAULT_HTTP_AUTHENTICATOR << "' HTTP framework authenticator"; - } - - LOG(INFO) << "Using default '" << DEFAULT_HTTP_AUTHENTICATOR - << "' HTTP framework authenticator"; - - Try<authentication::Authenticator*> authenticator = - BasicAuthenticatorFactory::create( - DEFAULT_HTTP_FRAMEWORK_AUTHENTICATION_REALM, - credentials.get()); - - if (authenticator.isError()) { - EXIT(EXIT_FAILURE) - << "Could not create HTTP framework authenticator module '" - << httpFrameworkAuthenticatorNames[0] << "': " - << authenticator.error(); - } - - httpFrameworkAuthenticator = authenticator.get(); - } else { - Try<authentication::Authenticator*> module = - modules::ModuleManager::create<authentication::Authenticator>( - httpFrameworkAuthenticatorNames[0]); - - if (module.isError()) { - EXIT(EXIT_FAILURE) - << "Could not create HTTP framework authenticator module '" - << httpFrameworkAuthenticatorNames[0] << "': " << module.error(); - } - - LOG(INFO) << "Using '" << httpFrameworkAuthenticatorNames[0] - << "' HTTP framework authenticator"; - - httpFrameworkAuthenticator = module.get(); - } - - CHECK_SOME(httpFrameworkAuthenticator); - - if (httpFrameworkAuthenticator.get() != nullptr) { - // Ownership of the `httpFrameworkAuthenticator` is passed to libprocess. - process::http::authentication::setAuthenticator( - DEFAULT_HTTP_FRAMEWORK_AUTHENTICATION_REALM, - Owned<authentication::Authenticator>( - httpFrameworkAuthenticator.get())); - } - - httpFrameworkAuthenticator = None(); + registerHttpAuthenticator( + DEFAULT_HTTP_FRAMEWORK_AUTHENTICATION_REALM, + credentials, + strings::split(flags.http_framework_authenticators.get(), ",")); } if (authorizer.isSome()) { @@ -968,7 +909,7 @@ void Master::initialize() // TODO(benh): Is this authentication realm sufficient or do // we need some kind of hybrid if we expect both schedulers // and operators/tooling to use this endpoint? - DEFAULT_HTTP_AUTHENTICATION_REALM, + READWRITE_HTTP_AUTHENTICATION_REALM, Http::API_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { @@ -984,7 +925,7 @@ void Master::initialize() return http.scheduler(request, principal); }); route("/create-volumes", - DEFAULT_HTTP_AUTHENTICATION_REALM, + READWRITE_HTTP_AUTHENTICATION_REALM, Http::CREATE_VOLUMES_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { @@ -992,7 +933,7 @@ void Master::initialize() return http.createVolumes(request, principal); }); route("/destroy-volumes", - DEFAULT_HTTP_AUTHENTICATION_REALM, + READWRITE_HTTP_AUTHENTICATION_REALM, Http::DESTROY_VOLUMES_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { @@ -1000,7 +941,7 @@ void Master::initialize() return http.destroyVolumes(request, principal); }); route("/frameworks", - DEFAULT_HTTP_AUTHENTICATION_REALM, + READONLY_HTTP_AUTHENTICATION_REALM, Http::FRAMEWORKS_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { @@ -1008,7 +949,7 @@ void Master::initialize() return http.frameworks(request, principal); }); route("/flags", - DEFAULT_HTTP_AUTHENTICATION_REALM, + READONLY_HTTP_AUTHENTICATION_REALM, Http::FLAGS_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { @@ -1026,7 +967,7 @@ void Master::initialize() return http.redirect(request); }); route("/reserve", - DEFAULT_HTTP_AUTHENTICATION_REALM, + READWRITE_HTTP_AUTHENTICATION_REALM, Http::RESERVE_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { @@ -1036,7 +977,7 @@ void Master::initialize() // TODO(ijimenez): Remove this endpoint at the end of the // deprecation cycle on 0.26. route("/roles.json", - DEFAULT_HTTP_AUTHENTICATION_REALM, + READONLY_HTTP_AUTHENTICATION_REALM, Http::ROLES_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { @@ -1044,7 +985,7 @@ void Master::initialize() return http.roles(request, principal); }); route("/roles", - DEFAULT_HTTP_AUTHENTICATION_REALM, + READONLY_HTTP_AUTHENTICATION_REALM, Http::ROLES_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { @@ -1052,7 +993,7 @@ void Master::initialize() return http.roles(request, principal); }); route("/teardown", - DEFAULT_HTTP_AUTHENTICATION_REALM, + READWRITE_HTTP_AUTHENTICATION_REALM, Http::TEARDOWN_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { @@ -1060,7 +1001,7 @@ void Master::initialize() return http.teardown(request, principal); }); route("/slaves", - DEFAULT_HTTP_AUTHENTICATION_REALM, + READONLY_HTTP_AUTHENTICATION_REALM, Http::SLAVES_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { @@ -1070,7 +1011,7 @@ void Master::initialize() // TODO(ijimenez): Remove this endpoint at the end of the // deprecation cycle on 0.26. route("/state.json", - DEFAULT_HTTP_AUTHENTICATION_REALM, + READONLY_HTTP_AUTHENTICATION_REALM, Http::STATE_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { @@ -1078,7 +1019,7 @@ void Master::initialize() return http.state(request, principal); }); route("/state", - DEFAULT_HTTP_AUTHENTICATION_REALM, + READONLY_HTTP_AUTHENTICATION_REALM, Http::STATE_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { @@ -1086,7 +1027,7 @@ void Master::initialize() return http.state(request, principal); }); route("/state-summary", - DEFAULT_HTTP_AUTHENTICATION_REALM, + READONLY_HTTP_AUTHENTICATION_REALM, Http::STATESUMMARY_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { @@ -1096,7 +1037,7 @@ void Master::initialize() // TODO(ijimenez): Remove this endpoint at the end of the // deprecation cycle. route("/tasks.json", - DEFAULT_HTTP_AUTHENTICATION_REALM, + READONLY_HTTP_AUTHENTICATION_REALM, Http::TASKS_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { @@ -1104,7 +1045,7 @@ void Master::initialize() return http.tasks(request, principal); }); route("/tasks", - DEFAULT_HTTP_AUTHENTICATION_REALM, + READONLY_HTTP_AUTHENTICATION_REALM, Http::TASKS_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { @@ -1112,7 +1053,7 @@ void Master::initialize() return http.tasks(request, principal); }); route("/maintenance/schedule", - DEFAULT_HTTP_AUTHENTICATION_REALM, + READWRITE_HTTP_AUTHENTICATION_REALM, Http::MAINTENANCE_SCHEDULE_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { @@ -1120,7 +1061,7 @@ void Master::initialize() return http.maintenanceSchedule(request, principal); }); route("/maintenance/status", - DEFAULT_HTTP_AUTHENTICATION_REALM, + READONLY_HTTP_AUTHENTICATION_REALM, Http::MAINTENANCE_STATUS_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { @@ -1128,7 +1069,7 @@ void Master::initialize() return http.maintenanceStatus(request, principal); }); route("/machine/down", - DEFAULT_HTTP_AUTHENTICATION_REALM, + READWRITE_HTTP_AUTHENTICATION_REALM, Http::MACHINE_DOWN_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { @@ -1136,7 +1077,7 @@ void Master::initialize() return http.machineDown(request, principal); }); route("/machine/up", - DEFAULT_HTTP_AUTHENTICATION_REALM, + READWRITE_HTTP_AUTHENTICATION_REALM, Http::MACHINE_UP_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { @@ -1144,7 +1085,7 @@ void Master::initialize() return http.machineUp(request, principal); }); route("/unreserve", - DEFAULT_HTTP_AUTHENTICATION_REALM, + READWRITE_HTTP_AUTHENTICATION_REALM, Http::UNRESERVE_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { @@ -1152,7 +1093,7 @@ void Master::initialize() return http.unreserve(request, principal); }); route("/quota", - DEFAULT_HTTP_AUTHENTICATION_REALM, + READWRITE_HTTP_AUTHENTICATION_REALM, Http::QUOTA_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { @@ -1160,7 +1101,7 @@ void Master::initialize() return http.quota(request, principal); }); route("/weights", - DEFAULT_HTTP_AUTHENTICATION_REALM, + READWRITE_HTTP_AUTHENTICATION_REALM, Http::WEIGHTS_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { http://git-wip-us.apache.org/repos/asf/mesos/blob/67369c01/src/master/master.hpp ---------------------------------------------------------------------- diff --git a/src/master/master.hpp b/src/master/master.hpp index 46621b8..bed6b8a 100644 --- a/src/master/master.hpp +++ b/src/master/master.hpp @@ -1870,6 +1870,13 @@ private: process::Future<Option<Error>> validate( const FrameworkInfo& frameworkInfo, const process::UPID& from); + + // Helper function to create HTTP authenticator + // for a given realm and register in libprocess. + void registerHttpAuthenticator( + const std::string& realm, + const Option<Credentials>& credentials, + const std::vector<std::string>& authenticatorNames); }; http://git-wip-us.apache.org/repos/asf/mesos/blob/67369c01/src/slave/constants.hpp ---------------------------------------------------------------------- diff --git a/src/slave/constants.hpp b/src/slave/constants.hpp index 1031939..46ef54e 100644 --- a/src/slave/constants.hpp +++ b/src/slave/constants.hpp @@ -127,8 +127,11 @@ constexpr char DEFAULT_AUTHORIZER[] = "local"; // Name of the default HTTP authenticator. constexpr char DEFAULT_HTTP_AUTHENTICATOR[] = "basic"; -// Name of the default agent HTTP authentication realm. -constexpr char DEFAULT_HTTP_AUTHENTICATION_REALM[] = "mesos-agent"; +// Name of the agent HTTP authentication realm for read-only endpoints. +constexpr char READONLY_HTTP_AUTHENTICATION_REALM[] = "mesos-agent-readonly"; + +// Name of the agent HTTP authentication realm for read-write endpoints. +constexpr char READWRITE_HTTP_AUTHENTICATION_REALM[] = "mesos-agent-readwrite"; // Default maximum storage space to be used by the fetcher cache. constexpr Bytes DEFAULT_FETCHER_CACHE_SIZE = Gigabytes(2); http://git-wip-us.apache.org/repos/asf/mesos/blob/67369c01/src/slave/flags.cpp ---------------------------------------------------------------------- diff --git a/src/slave/flags.cpp b/src/slave/flags.cpp index 84dbb2d..d7714de 100644 --- a/src/slave/flags.cpp +++ b/src/slave/flags.cpp @@ -786,11 +786,18 @@ mesos::internal::slave::Flags::Flags() "Currently there is no support for multiple HTTP authenticators.", DEFAULT_HTTP_AUTHENTICATOR); - add(&Flags::authenticate_http, - "authenticate_http", - "If `true`, only authenticated requests for HTTP endpoints supporting\n" - "authentication are allowed. If `false`, unauthenticated requests to\n" - "HTTP endpoints are also allowed.", + add(&Flags::authenticate_http_readwrite, + "authenticate_http_readwrite", + "If `true`, only authenticated requests for read-write HTTP endpoints\n" + "supporting authentication are allowed. If `false`, unauthenticated\n" + "requests to such HTTP endpoints are also allowed.", + false); + + add(&Flags::authenticate_http_readonly, + "authenticate_http_readonly", + "If `true`, only authenticated requests for read-only HTTP endpoints\n" + "supporting authentication are allowed. If `false`, unauthenticated\n" + "requests to such HTTP endpoints are also allowed.", false); add(&Flags::http_credentials, http://git-wip-us.apache.org/repos/asf/mesos/blob/67369c01/src/slave/flags.hpp ---------------------------------------------------------------------- diff --git a/src/slave/flags.hpp b/src/slave/flags.hpp index e798dbf..58fba4a 100644 --- a/src/slave/flags.hpp +++ b/src/slave/flags.hpp @@ -139,7 +139,8 @@ public: std::string authenticatee; std::string authorizer; std::string http_authenticators; - bool authenticate_http; + bool authenticate_http_readonly; + bool authenticate_http_readwrite; Option<Path> http_credentials; Option<std::string> hooks; Option<std::string> resource_estimator; http://git-wip-us.apache.org/repos/asf/mesos/blob/67369c01/src/slave/main.cpp ---------------------------------------------------------------------- diff --git a/src/slave/main.cpp b/src/slave/main.cpp index 4624392..a4d971a 100644 --- a/src/slave/main.cpp +++ b/src/slave/main.cpp @@ -256,7 +256,10 @@ int main(int argc, char** argv) // If `process::initialize()` returns `false`, then it was called before this // invocation, meaning the authentication realm for libprocess-level HTTP // endpoints was set incorrectly. This should be the first invocation. - if (!process::initialize(id, DEFAULT_HTTP_AUTHENTICATION_REALM)) { + if (!process::initialize( + id, + READWRITE_HTTP_AUTHENTICATION_REALM, + READONLY_HTTP_AUTHENTICATION_REALM)) { EXIT(EXIT_FAILURE) << "The call to `process::initialize()` in the agent's " << "`main()` was not the function's first invocation"; } @@ -405,7 +408,7 @@ int main(int argc, char** argv) createAuthorizationCallbacks(authorizer_.get())); } - Files files(DEFAULT_HTTP_AUTHENTICATION_REALM, authorizer_); + Files files(READONLY_HTTP_AUTHENTICATION_REALM, authorizer_); GarbageCollector gc; StatusUpdateManager statusUpdateManager(flags); http://git-wip-us.apache.org/repos/asf/mesos/blob/67369c01/src/slave/slave.cpp ---------------------------------------------------------------------- diff --git a/src/slave/slave.cpp b/src/slave/slave.cpp index 02982d5..0a0bc70 100644 --- a/src/slave/slave.cpp +++ b/src/slave/slave.cpp @@ -345,109 +345,33 @@ void Slave::initialize() } } - vector<string> httpAuthenticatorNames = - strings::split(flags.http_authenticators, ","); - - // If the `http_authenticators` flag is not specified, the default value will - // be filled in. Passing an empty string into the `http_authenticators` flag - // is considered an error. - if (httpAuthenticatorNames.empty()) { - EXIT(EXIT_FAILURE) << "No HTTP authenticator specified"; - } - if (httpAuthenticatorNames.size() > 1) { - EXIT(EXIT_FAILURE) << "Multiple HTTP authenticators not supported"; - } - if (httpAuthenticatorNames[0] != DEFAULT_HTTP_AUTHENTICATOR && - !modules::ModuleManager::contains<authentication::Authenticator>( - httpAuthenticatorNames[0])) { - EXIT(EXIT_FAILURE) - << "HTTP authenticator '" << httpAuthenticatorNames[0] << "'" - << " not found. Check the spelling (compare to '" - << DEFAULT_HTTP_AUTHENTICATOR << "') or verify that the" - << " authenticator was loaded successfully (see --modules)"; - } - - if (flags.authenticate_http) { - authentication::Authenticator* httpAuthenticator = nullptr; - - if (httpAuthenticatorNames[0] == DEFAULT_HTTP_AUTHENTICATOR) { - // Load credentials for HTTP authentication. - Credentials httpCredentials; - if (flags.http_credentials.isSome()) { - Result<Credentials> credentials = - credentials::read(flags.http_credentials.get()); - if (credentials.isError()) { - EXIT(EXIT_FAILURE) - << credentials.error() << " (see --http_credentials flag)"; - } else if (credentials.isNone()) { - EXIT(EXIT_FAILURE) - << "Credentials file must contain at least one credential" - << " (see --http_credentials flag)"; - } - - httpCredentials = credentials.get(); - } else { - EXIT(EXIT_FAILURE) - << "No credentials provided for the default '" - << DEFAULT_HTTP_AUTHENTICATOR << "' HTTP authenticator"; - } - - LOG(INFO) << "Using default '" << DEFAULT_HTTP_AUTHENTICATOR - << "' HTTP authenticator"; - - Try<authentication::Authenticator*> authenticator = - BasicAuthenticatorFactory::create( - DEFAULT_HTTP_AUTHENTICATION_REALM, - httpCredentials); - if (authenticator.isError()) { - EXIT(EXIT_FAILURE) - << "Could not create HTTP authenticator module '" - << httpAuthenticatorNames[0] << "': " << authenticator.error(); - } - - httpAuthenticator = authenticator.get(); - } else { - if (flags.http_credentials.isSome()) { - EXIT(EXIT_FAILURE) - << "The '--http_credentials' flag is only used by the default" - << " basic HTTP authenticator, but a custom authenticator" - << " module was specified via '--http_authenticators'"; - } - - Try<authentication::Authenticator*> module = - modules::ModuleManager::create<authentication::Authenticator>( - httpAuthenticatorNames[0]); - if (module.isError()) { - EXIT(EXIT_FAILURE) - << "Could not create HTTP authenticator module '" - << httpAuthenticatorNames[0] << "': " << module.error(); - } - - LOG(INFO) << "Using '" << httpAuthenticatorNames[0] - << "' HTTP authenticator"; - - httpAuthenticator = module.get(); + Option<Credentials> httpCredentials; + if (flags.http_credentials.isSome()) { + Result<Credentials> credentials = + credentials::read(flags.http_credentials.get()); + if (credentials.isError()) { + EXIT(EXIT_FAILURE) + << credentials.error() << " (see --http_credentials flag)"; + } else if (credentials.isNone()) { + EXIT(EXIT_FAILURE) + << "Credentials file must contain at least one credential" + << " (see --http_credentials flag)"; } + httpCredentials = credentials.get(); + } - if (httpAuthenticator == nullptr) { - EXIT(EXIT_FAILURE) - << "An error occurred while initializing the '" - << httpAuthenticatorNames[0] << "' HTTP authenticator"; - } + if (flags.authenticate_http_readonly) { + registerHttpAuthenticator( + READONLY_HTTP_AUTHENTICATION_REALM, + httpCredentials, + strings::split(flags.http_authenticators, ",")); + } - // Ownership of the `httpAuthenticator` is passed to libprocess. - process::http::authentication::setAuthenticator( - DEFAULT_HTTP_AUTHENTICATION_REALM, - Owned<authentication::Authenticator>(httpAuthenticator)); - } else if (flags.http_credentials.isSome()) { - EXIT(EXIT_FAILURE) - << "The '--http_credentials' flag was provided, but HTTP" - << " authentication was not enabled via '--authenticate_http'"; - } else if (httpAuthenticatorNames[0] != DEFAULT_HTTP_AUTHENTICATOR) { - EXIT(EXIT_FAILURE) - << "A custom HTTP authenticator was specified with the" - << " '--http_authenticators' flag, but HTTP authentication was not" - << " enabled via '--authenticate_http'"; + if (flags.authenticate_http_readwrite) { + registerHttpAuthenticator( + READWRITE_HTTP_AUTHENTICATION_REALM, + httpCredentials, + strings::split(flags.http_authenticators, ",")); } if ((flags.gc_disk_headroom < 0) || (flags.gc_disk_headroom > 1)) { @@ -708,7 +632,7 @@ void Slave::initialize() // TODO(benh): Is this authentication realm sufficient or do // we need some kind of hybrid if we expect both executors // and operators/tooling to use this endpoint? - DEFAULT_HTTP_AUTHENTICATION_REALM, + READWRITE_HTTP_AUTHENTICATION_REALM, Http::API_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { @@ -726,7 +650,7 @@ void Slave::initialize() // TODO(ijimenez): Remove this endpoint at the end of the // deprecation cycle on 0.26. route("/state.json", - DEFAULT_HTTP_AUTHENTICATION_REALM, + READONLY_HTTP_AUTHENTICATION_REALM, Http::STATE_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { @@ -734,7 +658,7 @@ void Slave::initialize() return http.state(request, principal); }); route("/state", - DEFAULT_HTTP_AUTHENTICATION_REALM, + READONLY_HTTP_AUTHENTICATION_REALM, Http::STATE_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { @@ -742,7 +666,7 @@ void Slave::initialize() return http.state(request, principal); }); route("/flags", - DEFAULT_HTTP_AUTHENTICATION_REALM, + READONLY_HTTP_AUTHENTICATION_REALM, Http::FLAGS_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { @@ -755,7 +679,7 @@ void Slave::initialize() return http.health(request); }); route("/monitor/statistics", - DEFAULT_HTTP_AUTHENTICATION_REALM, + READONLY_HTTP_AUTHENTICATION_REALM, Http::STATISTICS_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { @@ -764,14 +688,14 @@ void Slave::initialize() // TODO(ijimenez): Remove this endpoint at the end of the // deprecation cycle on 0.26. route("/monitor/statistics.json", - DEFAULT_HTTP_AUTHENTICATION_REALM, + READONLY_HTTP_AUTHENTICATION_REALM, Http::STATISTICS_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { return http.statistics(request, principal); }); route("/containers", - DEFAULT_HTTP_AUTHENTICATION_REALM, + READONLY_HTTP_AUTHENTICATION_REALM, Http::CONTAINERS_HELP(), [this](const process::http::Request& request, const Option<string>& principal) { @@ -836,6 +760,77 @@ void Slave::initialize() .onAny(defer(self(), &Slave::__recover, lambda::_1)); } + +void Slave::registerHttpAuthenticator( + const string& realm, + const Option<Credentials>& credentials, + const vector<string>& httpAuthenticatorNames) +{ + // At least the default authenticator is always specified. + // Passing an empty string into the `http_authenticators` + // flag is considered an error. + if (httpAuthenticatorNames.empty()) { + EXIT(EXIT_FAILURE) + << "No HTTP authenticator specified for realm '" << realm << "'"; + } + if (httpAuthenticatorNames.size() > 1) { + EXIT(EXIT_FAILURE) << "Multiple HTTP authenticators not supported"; + } + if (httpAuthenticatorNames[0] != DEFAULT_HTTP_AUTHENTICATOR && + !modules::ModuleManager::contains<authentication::Authenticator>( + httpAuthenticatorNames[0])) { + EXIT(EXIT_FAILURE) + << "HTTP authenticator '" << httpAuthenticatorNames[0] << "' not" + << " found. Check the spelling (compare to '" + << DEFAULT_HTTP_AUTHENTICATOR << "') or verify that the" + << " authenticator was loaded successfully (see --modules)"; + } + + Option<authentication::Authenticator*> httpAuthenticator; + if (httpAuthenticatorNames[0] == DEFAULT_HTTP_AUTHENTICATOR) { + if (credentials.isNone()) { + EXIT(EXIT_FAILURE) + << "No credentials provided for the default '" + << DEFAULT_HTTP_AUTHENTICATOR << "' HTTP authenticator for realm '" + << realm << "'"; + } + + LOG(INFO) << "Using default '" << DEFAULT_HTTP_AUTHENTICATOR + << "' HTTP authenticator for realm '" << realm << "'"; + + Try<authentication::Authenticator*> authenticator = + BasicAuthenticatorFactory::create(realm, credentials.get()); + if (authenticator.isError()) { + EXIT(EXIT_FAILURE) + << "Could not create HTTP authenticator module '" + << httpAuthenticatorNames[0] << "': " << authenticator.error(); + } + + httpAuthenticator = authenticator.get(); + } else { + Try<authentication::Authenticator*> module = + modules::ModuleManager::create<authentication::Authenticator>( + httpAuthenticatorNames[0]); + if (module.isError()) { + EXIT(EXIT_FAILURE) + << "Could not create HTTP authenticator module '" + << httpAuthenticatorNames[0] << "': " << module.error(); + } + LOG(INFO) << "Using '" << httpAuthenticatorNames[0] + << "' HTTP authenticator for realm '" << realm << "'"; + httpAuthenticator = module.get(); + } + + if (httpAuthenticator.isSome() && httpAuthenticator.get() != nullptr) { + // Ownership of the `httpAuthenticator` is passed to libprocess. + process::http::authentication::setAuthenticator( + realm, + Owned<authentication::Authenticator>(httpAuthenticator.get())); + httpAuthenticator = None(); + } +} + + void Slave::finalize() { LOG(INFO) << "Agent terminating"; http://git-wip-us.apache.org/repos/asf/mesos/blob/67369c01/src/slave/slave.hpp ---------------------------------------------------------------------- diff --git a/src/slave/slave.hpp b/src/slave/slave.hpp index a8952f0..f9f725b 100644 --- a/src/slave/slave.hpp +++ b/src/slave/slave.hpp @@ -411,6 +411,13 @@ public: const ContainerID& containerId); private: + // Helper function to create HTTP authenticator + // for a given realm and register in libprocess. + void registerHttpAuthenticator( + const std::string& realm, + const Option<Credentials>& credentials, + const std::vector<std::string>& authenticatorNames); + void _authenticate(); void authenticationTimeout(process::Future<bool> future); http://git-wip-us.apache.org/repos/asf/mesos/blob/67369c01/src/tests/cluster.cpp ---------------------------------------------------------------------- diff --git a/src/tests/cluster.cpp b/src/tests/cluster.cpp index e1be275..dd41544 100644 --- a/src/tests/cluster.cpp +++ b/src/tests/cluster.cpp @@ -242,7 +242,7 @@ Try<process::Owned<Master>> Master::start( // Instantiate some other master dependencies. master->state.reset(new mesos::state::protobuf::State(master->storage.get())); master->registrar.reset(new master::Registrar( - flags, master->state.get(), master::DEFAULT_HTTP_AUTHENTICATION_REALM)); + flags, master->state.get(), master::READONLY_HTTP_AUTHENTICATION_REALM)); if (slaveRemovalLimiter.isNone() && flags.agent_removal_rate_limit.isSome()) { // Parse the flag value. @@ -341,7 +341,9 @@ Master::~Master() // NOTE: Authenticators' lifetimes are tied to libprocess's lifetime. // This means that multiple masters in tests are not supported. process::http::authentication::unsetAuthenticator( - master::DEFAULT_HTTP_AUTHENTICATION_REALM); + master::READONLY_HTTP_AUTHENTICATION_REALM); + process::http::authentication::unsetAuthenticator( + master::READWRITE_HTTP_AUTHENTICATION_REALM); process::terminate(pid); process::wait(pid); @@ -518,6 +520,11 @@ Slave::~Slave() process::http::authorization::unsetCallbacks(); } + process::http::authentication::unsetAuthenticator( + slave::READONLY_HTTP_AUTHENTICATION_REALM); + process::http::authentication::unsetAuthenticator( + slave::READWRITE_HTTP_AUTHENTICATION_REALM); + // If either `shutdown()` or `terminate()` were called already, // skip the below container cleanup logic. Additionally, we can skip // termination, as the shutdown/terminate will do this too. http://git-wip-us.apache.org/repos/asf/mesos/blob/67369c01/src/tests/cluster.hpp ---------------------------------------------------------------------- diff --git a/src/tests/cluster.hpp b/src/tests/cluster.hpp index 55dbaae..c6fbbf2 100644 --- a/src/tests/cluster.hpp +++ b/src/tests/cluster.hpp @@ -102,7 +102,7 @@ public: void setAuthorizationCallbacks(Authorizer* authorizer); private: - Master() : files(master::DEFAULT_HTTP_AUTHENTICATION_REALM) {}; + Master() : files(master::READONLY_HTTP_AUTHENTICATION_REALM) {}; // Not copyable, not assignable. Master(const Master&) = delete; @@ -175,7 +175,7 @@ public: void setAuthorizationCallbacks(Authorizer* authorizer); private: - Slave() : files(slave::DEFAULT_HTTP_AUTHENTICATION_REALM) {}; + Slave() : files(slave::READONLY_HTTP_AUTHENTICATION_REALM) {}; // Not copyable, not assignable. Slave(const Slave&) = delete; http://git-wip-us.apache.org/repos/asf/mesos/blob/67369c01/src/tests/dynamic_weights_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/dynamic_weights_tests.cpp b/src/tests/dynamic_weights_tests.cpp index 6aa0102..88565d1 100644 --- a/src/tests/dynamic_weights_tests.cpp +++ b/src/tests/dynamic_weights_tests.cpp @@ -556,7 +556,7 @@ TEST_F(DynamicWeightsTest, AuthorizedUpdateWeightRequestWithoutPrincipal) // Disable authentication and set acls. master::Flags masterFlags = CreateMasterFlags(); - masterFlags.authenticate_http = false; + masterFlags.authenticate_http_readwrite = false; masterFlags.acls = acls; Try<Owned<cluster::Master>> master = StartMaster(masterFlags); http://git-wip-us.apache.org/repos/asf/mesos/blob/67369c01/src/tests/logging_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/logging_tests.cpp b/src/tests/logging_tests.cpp index 8712d33..886dcd0 100644 --- a/src/tests/logging_tests.cpp +++ b/src/tests/logging_tests.cpp @@ -144,8 +144,8 @@ TEST_F(LoggingTest, ToggleAuthenticationEnabled) credentials.add_credentials()->CopyFrom(DEFAULT_CREDENTIAL); // Create a basic HTTP authenticator with the specified credentials and set it - // as the authenticator for `DEFAULT_HTTP_AUTHENTICATION_REALM`. - setBasicHttpAuthenticator(DEFAULT_HTTP_AUTHENTICATION_REALM, credentials); + // as the authenticator for `READWRITE_HTTP_AUTHENTICATION_REALM`. + setBasicHttpAuthenticator(READWRITE_HTTP_AUTHENTICATION_REALM, credentials); process::PID<> pid; pid.id = "logging"; @@ -165,8 +165,8 @@ TEST_F(LoggingTest, ToggleAuthorizationEnabled) credentials.add_credentials()->CopyFrom(DEFAULT_CREDENTIAL); // Create a basic HTTP authenticator with the specified credentials and set it - // as the authenticator for `DEFAULT_HTTP_AUTHENTICATION_REALM`. - setBasicHttpAuthenticator(DEFAULT_HTTP_AUTHENTICATION_REALM, credentials); + // as the authenticator for `READWRITE_HTTP_AUTHENTICATION_REALM`. + setBasicHttpAuthenticator(READWRITE_HTTP_AUTHENTICATION_REALM, credentials); ACLs acls; http://git-wip-us.apache.org/repos/asf/mesos/blob/67369c01/src/tests/main.cpp ---------------------------------------------------------------------- diff --git a/src/tests/main.cpp b/src/tests/main.cpp index 1425a04..e1507ba 100644 --- a/src/tests/main.cpp +++ b/src/tests/main.cpp @@ -93,7 +93,10 @@ int main(int argc, char** argv) // If `process::initialize()` returns `false`, then it was called before this // invocation, meaning the authentication realm for libprocess-level HTTP // endpoints was set incorrectly. This should be the first invocation. - if (!process::initialize(None(), DEFAULT_HTTP_AUTHENTICATION_REALM)) { + if (!process::initialize( + None(), + READWRITE_HTTP_AUTHENTICATION_REALM, + READONLY_HTTP_AUTHENTICATION_REALM)) { EXIT(EXIT_FAILURE) << "The call to `process::initialize()` in the tests' " << "`main()` was not the function's first invocation"; } http://git-wip-us.apache.org/repos/asf/mesos/blob/67369c01/src/tests/master_quota_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/master_quota_tests.cpp b/src/tests/master_quota_tests.cpp index 639f4c4..48be740 100644 --- a/src/tests/master_quota_tests.cpp +++ b/src/tests/master_quota_tests.cpp @@ -1043,15 +1043,13 @@ TEST_F(MasterQuotaTest, NoAuthenticationNoAuthorization) TestAllocator<> allocator; EXPECT_CALL(allocator, initialize(_, _, _, _, _)); - // Disable authentication and authorization. + // Disable http_readwrite authentication and authorization. // TODO(alexr): Setting master `--acls` flag to `ACLs()` or `None()` seems // to be semantically equal, however, the test harness currently does not // allow `None()`. Once MESOS-4196 is resolved, use `None()` for clarity. master::Flags masterFlags = CreateMasterFlags(); masterFlags.acls = ACLs(); - masterFlags.authenticate_http = false; - masterFlags.authenticate_http_frameworks = false; - masterFlags.credentials = None(); + masterFlags.authenticate_http_readwrite = false; Try<Owned<cluster::Master>> master = StartMaster(&allocator, masterFlags); ASSERT_SOME(master); @@ -1538,7 +1536,8 @@ TEST_F(MasterQuotaTest, AuthorizeGetUpdateQuotaRequestsWithoutPrincipal) master::Flags masterFlags = CreateMasterFlags(); masterFlags.acls = acls; - masterFlags.authenticate_http = false; + masterFlags.authenticate_http_readonly = false; + masterFlags.authenticate_http_readwrite = false; masterFlags.authenticate_http_frameworks = false; masterFlags.credentials = None(); @@ -1632,7 +1631,8 @@ TEST_F(MasterQuotaTest, AuthorizeSetRemoveQuotaRequestsWithoutPrincipal) master::Flags masterFlags = CreateMasterFlags(); masterFlags.acls = acls; - masterFlags.authenticate_http = false; + masterFlags.authenticate_http_readonly = false; + masterFlags.authenticate_http_readwrite = false; masterFlags.authenticate_http_frameworks = false; masterFlags.credentials = None(); http://git-wip-us.apache.org/repos/asf/mesos/blob/67369c01/src/tests/master_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/master_tests.cpp b/src/tests/master_tests.cpp index 252b8f9..6709818 100644 --- a/src/tests/master_tests.cpp +++ b/src/tests/master_tests.cpp @@ -4451,7 +4451,7 @@ TEST_F(MasterTest, MaxCompletedTasksPerFrameworkFlag) } -// Test get requests on various endpoints without authentication and +// Test GET requests on various endpoints without authentication and // with bad credentials. // Note that we have similar checks for the maintenance, roles, quota, teardown, // reserve, unreserve, create-volumes, destroy-volumes, observe endpoints in the @@ -4576,6 +4576,62 @@ TEST_F(MasterTest, EndpointsBadAuthentication) } +// Test unauthenticated GET requests on various endpoints +// when authentication is disabled for read-only endpoints. +TEST_F(MasterTest, ReadonlyEndpointsNoAuthentication) +{ + // Set up a master with authentication disabled for read-only endpoints. + master::Flags masterFlags = CreateMasterFlags(); + masterFlags.authenticate_http_readonly = false; + + Try<Owned<cluster::Master>> master = StartMaster(masterFlags); + ASSERT_SOME(master); + + // `state` endpoint from master should be allowed without authentication. + { + Future<Response> response = process::http::get(master.get()->pid, "state"); + + AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); + } + + // `quota` endpoint from master is controlled by `authenticate_http_readwrite` + // flag which is set to true, so an unauthenticated request will be rejected. + { + Future<Response> response = process::http::get(master.get()->pid, "quota"); + + AWAIT_EXPECT_RESPONSE_STATUS_EQ(Unauthorized({}).status, response); + } +} + + +// Test GET requests on various endpoints without authentication +// when authentication for read-write endpoints is disabled. +TEST_F(MasterTest, ReadwriteEndpointsNoAuthentication) +{ + // Set up a master with authentication disabled for read-write endpoints. + master::Flags masterFlags = CreateMasterFlags(); + masterFlags.authenticate_http_readwrite = false; + + Try<Owned<cluster::Master>> master = StartMaster(masterFlags); + ASSERT_SOME(master); + + // `quota` endpoint from master should be allowed without authentication. + { + Future<Response> response = process::http::get(master.get()->pid, "quota"); + + AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); + } + + // `state` endpoint from master is controlled by `authenticate_http_readonly` + // flag which is set to true, so an unauthenticated request will be rejected. + { + Future<Response> response = process::http::get(master.get()->pid, "state"); + + AWAIT_EXPECT_RESPONSE_STATUS_EQ(Unauthorized({}).status, response); + } +} + + TEST_F(MasterTest, RejectFrameworkWithInvalidFailoverTimeout) { Try<Owned<cluster::Master>> master = StartMaster(); http://git-wip-us.apache.org/repos/asf/mesos/blob/67369c01/src/tests/mesos.cpp ---------------------------------------------------------------------- diff --git a/src/tests/mesos.cpp b/src/tests/mesos.cpp index d073d79..30492d7 100644 --- a/src/tests/mesos.cpp +++ b/src/tests/mesos.cpp @@ -107,7 +107,8 @@ master::Flags MesosTest::CreateMasterFlags() CHECK_SOME(os::mkdir(flags.work_dir.get())); - flags.authenticate_http = true; + flags.authenticate_http_readonly = true; + flags.authenticate_http_readwrite = true; flags.authenticate_frameworks = true; flags.authenticate_agents = true; @@ -196,7 +197,8 @@ slave::Flags MesosTest::CreateSlaveFlags() flags.acls = ACLs(); } - flags.authenticate_http = true; + flags.authenticate_http_readonly = true; + flags.authenticate_http_readwrite = true; { // Create a default HTTP credentials file. @@ -529,7 +531,7 @@ MockSlave::MockSlave( &resourceEstimator, _qosController.isSome() ? _qosController.get() : &qosController, authorizer), - files(slave::DEFAULT_HTTP_AUTHENTICATION_REALM) + files(slave::READONLY_HTTP_AUTHENTICATION_REALM) { // Set up default behaviors, calling the original methods. EXPECT_CALL(*this, runTask(_, _, _, _, _)) http://git-wip-us.apache.org/repos/asf/mesos/blob/67369c01/src/tests/mesos.hpp ---------------------------------------------------------------------- diff --git a/src/tests/mesos.hpp b/src/tests/mesos.hpp index e4eccfc..51c66f1 100644 --- a/src/tests/mesos.hpp +++ b/src/tests/mesos.hpp @@ -98,7 +98,8 @@ namespace mesos { namespace internal { namespace tests { -constexpr char DEFAULT_HTTP_AUTHENTICATION_REALM[] = "test-realm"; +constexpr char READONLY_HTTP_AUTHENTICATION_REALM[] = "test-readonly-realm"; +constexpr char READWRITE_HTTP_AUTHENTICATION_REALM[] = "test-readwrite-realm"; // Forward declarations. class MockExecutor; http://git-wip-us.apache.org/repos/asf/mesos/blob/67369c01/src/tests/metrics_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/metrics_tests.cpp b/src/tests/metrics_tests.cpp index e470e75..27fc35f 100644 --- a/src/tests/metrics_tests.cpp +++ b/src/tests/metrics_tests.cpp @@ -276,8 +276,8 @@ TEST_F(MetricsTest, MasterAuthenticationEnabled) credentials.add_credentials()->CopyFrom(DEFAULT_CREDENTIAL); // Create a basic HTTP authenticator with the specified credentials and set it - // as the authenticator for `DEFAULT_HTTP_AUTHENTICATION_REALM`. - setBasicHttpAuthenticator(DEFAULT_HTTP_AUTHENTICATION_REALM, credentials); + // as the authenticator for `READONLY_HTTP_AUTHENTICATION_REALM`. + setBasicHttpAuthenticator(READONLY_HTTP_AUTHENTICATION_REALM, credentials); Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); @@ -301,8 +301,8 @@ TEST_F(MetricsTest, AgentAuthenticationEnabled) credentials.add_credentials()->CopyFrom(DEFAULT_CREDENTIAL); // Create a basic HTTP authenticator with the specified credentials and set it - // as the authenticator for `DEFAULT_HTTP_AUTHENTICATION_REALM`. - setBasicHttpAuthenticator(DEFAULT_HTTP_AUTHENTICATION_REALM, credentials); + // as the authenticator for `READONLY_HTTP_AUTHENTICATION_REALM`. + setBasicHttpAuthenticator(READONLY_HTTP_AUTHENTICATION_REALM, credentials); Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); @@ -330,8 +330,8 @@ TEST_F(MetricsTest, MasterAuthorizationEnabled) credentials.add_credentials()->CopyFrom(DEFAULT_CREDENTIAL); // Create a basic HTTP authenticator with the specified credentials and set it - // as the authenticator for `DEFAULT_HTTP_AUTHENTICATION_REALM`. - setBasicHttpAuthenticator(DEFAULT_HTTP_AUTHENTICATION_REALM, credentials); + // as the authenticator for `READONLY_HTTP_AUTHENTICATION_REALM`. + setBasicHttpAuthenticator(READONLY_HTTP_AUTHENTICATION_REALM, credentials); ACLs acls; @@ -370,8 +370,8 @@ TEST_F(MetricsTest, AgentAuthorizationEnabled) credentials.add_credentials()->CopyFrom(DEFAULT_CREDENTIAL); // Create a basic HTTP authenticator with the specified credentials and set it - // as the authenticator for `DEFAULT_HTTP_AUTHENTICATION_REALM`. - setBasicHttpAuthenticator(DEFAULT_HTTP_AUTHENTICATION_REALM, credentials); + // as the authenticator for `READONLY_HTTP_AUTHENTICATION_REALM`. + setBasicHttpAuthenticator(READONLY_HTTP_AUTHENTICATION_REALM, credentials); ACLs acls; http://git-wip-us.apache.org/repos/asf/mesos/blob/67369c01/src/tests/persistent_volume_endpoints_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/persistent_volume_endpoints_tests.cpp b/src/tests/persistent_volume_endpoints_tests.cpp index 2a22f3b..2348f13 100644 --- a/src/tests/persistent_volume_endpoints_tests.cpp +++ b/src/tests/persistent_volume_endpoints_tests.cpp @@ -1353,7 +1353,7 @@ TEST_F(PersistentVolumeEndpointsTest, NoAuthentication) // Create master flags that will disable authentication. master::Flags masterFlags = CreateMasterFlags(); - masterFlags.authenticate_http = false; + masterFlags.authenticate_http_readwrite = false; EXPECT_CALL(allocator, initialize(_, _, _, _, _)); http://git-wip-us.apache.org/repos/asf/mesos/blob/67369c01/src/tests/reservation_endpoints_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/reservation_endpoints_tests.cpp b/src/tests/reservation_endpoints_tests.cpp index 48c002d..bee5ea6 100644 --- a/src/tests/reservation_endpoints_tests.cpp +++ b/src/tests/reservation_endpoints_tests.cpp @@ -1450,7 +1450,7 @@ TEST_F(ReservationEndpointsTest, ReserveAndUnreserveNoAuthentication) // Create a master. master::Flags masterFlags = CreateMasterFlags(); masterFlags.authenticate_frameworks = false; - masterFlags.authenticate_http = false; + masterFlags.authenticate_http_readwrite = false; EXPECT_CALL(allocator, initialize(_, _, _, _, _)); http://git-wip-us.apache.org/repos/asf/mesos/blob/67369c01/src/tests/slave_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/slave_tests.cpp b/src/tests/slave_tests.cpp index 60f9e16..b9fa85d 100644 --- a/src/tests/slave_tests.cpp +++ b/src/tests/slave_tests.cpp @@ -1504,6 +1504,44 @@ TEST_F(SlaveTest, HTTPEndpointsBadAuthentication) } +// Tests that a client can talk to read-only endpoints when read-only +// authentication is disabled. +TEST_F(SlaveTest, ReadonlyHTTPEndpointsNoAuthentication) +{ + Try<Owned<cluster::Master>> master = StartMaster(); + ASSERT_SOME(master); + + // Capture the start time deterministically. + Clock::pause(); + + Future<Nothing> recover = FUTURE_DISPATCH(_, &Slave::__recover); + + Owned<MasterDetector> detector = master.get()->createDetector(); + + slave::Flags flags = CreateSlaveFlags(); + flags.authenticate_http_readonly = false; + + Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), flags); + ASSERT_SOME(slave); + + // Ensure slave has finished recovery. + AWAIT_READY(recover); + Clock::settle(); + + // Requests containing no authentication headers. + { + Future<Response> response = process::http::get(slave.get()->pid, "state"); + AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); + + response = process::http::get(slave.get()->pid, "flags"); + AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); + + response = process::http::get(slave.get()->pid, "containers"); + AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); + } +} + + // This test verifies correct handling of statistics endpoint when // there is no exeuctor running. TEST_F(SlaveTest, StatisticsEndpointNoExecutor)