Reorganized authentication implementation locations. For paving the way into further authentication mechanism implementations, the existing is reorganized.
Move src/sasl to src/authentication/cram_md5. Rename src/tests/sasl_tests.cpp to src/tests/cram_md5_authentication_tests.cpp. Adapt depending include paths. Rename specific test implementations from SASL to CRAMMD5Authentication. Rename sasl namespace to cram_md5 throughout all uses. Add missing license blob to auxprop.cpp. Review: https://reviews.apache.org/r/27122 Project: http://git-wip-us.apache.org/repos/asf/mesos/repo Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/b51f5550 Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/b51f5550 Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/b51f5550 Branch: refs/heads/master Commit: b51f55500b809b452123c011012227d821e02c04 Parents: 64dcf8e Author: Till Toenshoff <[email protected]> Authored: Mon Nov 3 14:39:42 2014 -0800 Committer: Adam B <[email protected]> Committed: Mon Nov 3 14:39:42 2014 -0800 ---------------------------------------------------------------------- src/Makefile.am | 10 +- src/authentication/cram_md5/authenticatee.hpp | 412 +++++++++++++++++ src/authentication/cram_md5/authenticator.hpp | 494 +++++++++++++++++++++ src/authentication/cram_md5/auxprop.cpp | 204 +++++++++ src/authentication/cram_md5/auxprop.hpp | 96 ++++ src/master/master.cpp | 11 +- src/master/master.hpp | 5 +- src/sasl/authenticatee.hpp | 412 ----------------- src/sasl/authenticator.hpp | 494 --------------------- src/sasl/auxprop.cpp | 186 -------- src/sasl/auxprop.hpp | 96 ---- src/sched/sched.cpp | 6 +- src/scheduler/scheduler.cpp | 6 +- src/slave/slave.cpp | 6 +- src/slave/slave.hpp | 6 +- src/tests/cram_md5_authentication_tests.cpp | 209 +++++++++ src/tests/sasl_tests.cpp | 209 --------- 17 files changed, 1441 insertions(+), 1421 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mesos/blob/b51f5550/src/Makefile.am ---------------------------------------------------------------------- diff --git a/src/Makefile.am b/src/Makefile.am index e6a0715..21e1e20 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -249,6 +249,10 @@ nodist_libmesos_no_3rdparty_la_SOURCES = \ $(REGISTRY_PROTOS) libmesos_no_3rdparty_la_SOURCES = \ + authentication/cram_md5/authenticatee.hpp \ + authentication/cram_md5/authenticator.hpp \ + authentication/cram_md5/auxprop.hpp \ + authentication/cram_md5/auxprop.cpp \ authorizer/authorizer.cpp \ common/attributes.cpp \ common/date_utils.cpp \ @@ -276,10 +280,6 @@ libmesos_no_3rdparty_la_SOURCES = \ master/registrar.cpp \ master/repairer.cpp \ module/manager.cpp \ - sasl/authenticatee.hpp \ - sasl/authenticator.hpp \ - sasl/auxprop.hpp \ - sasl/auxprop.cpp \ sched/sched.cpp \ scheduler/scheduler.cpp \ slave/constants.cpp \ @@ -1183,6 +1183,7 @@ mesos_tests_SOURCES = \ tests/composing_containerizer_tests.cpp \ tests/containerizer.cpp \ tests/containerizer_tests.cpp \ + tests/cram_md5_authentication_tests.cpp \ tests/credentials_tests.cpp \ tests/docker_containerizer_tests.cpp \ tests/docker_tests.cpp \ @@ -1217,7 +1218,6 @@ mesos_tests_SOURCES = \ tests/repair_tests.cpp \ tests/resource_offers_tests.cpp \ tests/resources_tests.cpp \ - tests/sasl_tests.cpp \ tests/scheduler_tests.cpp \ tests/script.cpp \ tests/shutdown_tests.cpp \ http://git-wip-us.apache.org/repos/asf/mesos/blob/b51f5550/src/authentication/cram_md5/authenticatee.hpp ---------------------------------------------------------------------- diff --git a/src/authentication/cram_md5/authenticatee.hpp b/src/authentication/cram_md5/authenticatee.hpp new file mode 100644 index 0000000..3088a77 --- /dev/null +++ b/src/authentication/cram_md5/authenticatee.hpp @@ -0,0 +1,412 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __AUTHENTICATION_CRAM_MD5_AUTHENTICATEE_HPP__ +#define __AUTHENTICATION_CRAM_MD5_AUTHENTICATEE_HPP__ + +#include <sasl/sasl.h> + +#include <mesos/mesos.hpp> + +#include <process/defer.hpp> +#include <process/future.hpp> +#include <process/id.hpp> +#include <process/once.hpp> +#include <process/process.hpp> +#include <process/protobuf.hpp> + +#include <stout/strings.hpp> + +#include "messages/messages.hpp" + +namespace mesos { +namespace internal { +namespace cram_md5 { + +// Forward declaration. +class AuthenticateeProcess; + + +class Authenticatee +{ +public: + // 'credential' is used to authenticate the 'client'. + Authenticatee(const Credential& credential, const process::UPID& client); + ~Authenticatee(); + + // Returns true if successfully authenticated otherwise false or an + // error. Note that we distinguish authentication failure (false) + // from a failed future in the event the future failed due to a + // transient error and authentication can (should) be + // retried. Discarding the future will cause the future to fail if + // it hasn't already completed since we have already started the + // authentication procedure and can't reliably cancel. + process::Future<bool> authenticate(const process::UPID& pid); + +private: + AuthenticateeProcess* process; +}; + + +class AuthenticateeProcess : public ProtobufProcess<AuthenticateeProcess> +{ +public: + AuthenticateeProcess(const Credential& _credential, + const process::UPID& _client) + : ProcessBase(process::ID::generate("authenticatee")), + credential(_credential), + client(_client), + status(READY), + connection(NULL) + { + const char* data = credential.secret().data(); + size_t length = credential.secret().length(); + + // Need to allocate the secret via 'malloc' because SASL is + // expecting the data appended to the end of the struct. *sigh* + secret = (sasl_secret_t*) malloc(sizeof(sasl_secret_t) + length); + + CHECK(secret != NULL) << "Failed to allocate memory for secret"; + + memcpy(secret->data, data, length); + secret->len = length; + } + + virtual ~AuthenticateeProcess() + { + if (connection != NULL) { + sasl_dispose(&connection); + } + free(secret); + } + + virtual void finalize() + { + discarded(); // Fail the promise. + } + + process::Future<bool> authenticate(const process::UPID& pid) + { + static process::Once* initialize = new process::Once(); + static bool initialized = false; + + if (!initialize->once()) { + LOG(INFO) << "Initializing client SASL"; + int result = sasl_client_init(NULL); + if (result != SASL_OK) { + status = ERROR; + std::string error(sasl_errstring(result, NULL, NULL)); + promise.fail("Failed to initialize SASL: " + error); + initialize->done(); + return promise.future(); + } + + initialized = true; + + initialize->done(); + } + + if (!initialized) { + promise.fail("Failed to initialize SASL"); + return promise.future(); + } + + if (status != READY) { + return promise.future(); + } + + LOG(INFO) << "Creating new client SASL connection"; + + callbacks[0].id = SASL_CB_GETREALM; + callbacks[0].proc = NULL; + callbacks[0].context = NULL; + + callbacks[1].id = SASL_CB_USER; + callbacks[1].proc = (int(*)()) &user; + callbacks[1].context = (void*) credential.principal().c_str(); + + // NOTE: Some SASL mechanisms do not allow/enable "proxying", + // i.e., authorization. Therefore, some mechanisms send _only_ the + // authoriation name rather than both the user (authentication + // name) and authorization name. Thus, for now, we assume + // authorization is handled out of band. Consider the + // SASL_NEED_PROXY flag if we want to reconsider this in the + // future. + callbacks[2].id = SASL_CB_AUTHNAME; + callbacks[2].proc = (int(*)()) &user; + callbacks[2].context = (void*) credential.principal().c_str(); + + callbacks[3].id = SASL_CB_PASS; + callbacks[3].proc = (int(*)()) &pass; + callbacks[3].context = (void*) secret; + + callbacks[4].id = SASL_CB_LIST_END; + callbacks[4].proc = NULL; + callbacks[4].context = NULL; + + int result = sasl_client_new( + "mesos", // Registered name of service. + NULL, // Server's FQDN. + NULL, NULL, // IP Address information strings. + callbacks, // Callbacks supported only for this connection. + 0, // Security flags (security layers are enabled + // using security properties, separately). + &connection); + + if (result != SASL_OK) { + status = ERROR; + std::string error(sasl_errstring(result, NULL, NULL)); + promise.fail("Failed to create client SASL connection: " + error); + return promise.future(); + } + + AuthenticateMessage message; + message.set_pid(client); + send(pid, message); + + status = STARTING; + + // Stop authenticating if nobody cares. + promise.future().onDiscard(defer(self(), &Self::discarded)); + + return promise.future(); + } + +protected: + virtual void initialize() + { + // Anticipate mechanisms and steps from the server. + install<AuthenticationMechanismsMessage>( + &AuthenticateeProcess::mechanisms, + &AuthenticationMechanismsMessage::mechanisms); + + install<AuthenticationStepMessage>( + &AuthenticateeProcess::step, + &AuthenticationStepMessage::data); + + install<AuthenticationCompletedMessage>( + &AuthenticateeProcess::completed); + + install<AuthenticationFailedMessage>( + &AuthenticateeProcess::failed); + + install<AuthenticationErrorMessage>( + &AuthenticateeProcess::error, + &AuthenticationErrorMessage::error); + } + + void mechanisms(const std::vector<std::string>& mechanisms) + { + if (status != STARTING) { + status = ERROR; + promise.fail("Unexpected authentication 'mechanisms' received"); + return; + } + + // TODO(benh): Store 'from' in order to ensure we only communicate + // with the same Authenticator. + + LOG(INFO) << "Received SASL authentication mechanisms: " + << strings::join(",", mechanisms); + + sasl_interact_t* interact = NULL; + const char* output = NULL; + unsigned length = 0; + const char* mechanism = NULL; + + int result = sasl_client_start( + connection, + strings::join(" ", mechanisms).c_str(), + &interact, // Set if an interaction is needed. + &output, // The output string (to send to server). + &length, // The length of the output string. + &mechanism); // The chosen mechanism. + + CHECK_NE(SASL_INTERACT, result) + << "Not expecting an interaction (ID: " << interact->id << ")"; + + if (result != SASL_OK && result != SASL_CONTINUE) { + std::string error(sasl_errdetail(connection)); + status = ERROR; + promise.fail("Failed to start the SASL client: " + error); + return; + } + + LOG(INFO) << "Attempting to authenticate with mechanism '" + << mechanism << "'"; + + AuthenticationStartMessage message; + message.set_mechanism(mechanism); + message.set_data(output, length); + + reply(message); + + status = STEPPING; + } + + void step(const std::string& data) + { + if (status != STEPPING) { + status = ERROR; + promise.fail("Unexpected authentication 'step' received"); + return; + } + + LOG(INFO) << "Received SASL authentication step"; + + sasl_interact_t* interact = NULL; + const char* output = NULL; + unsigned length = 0; + + int result = sasl_client_step( + connection, + data.length() == 0 ? NULL : data.data(), + data.length(), + &interact, + &output, + &length); + + CHECK_NE(SASL_INTERACT, result) + << "Not expecting an interaction (ID: " << interact->id << ")"; + + if (result == SASL_OK || result == SASL_CONTINUE) { + // We don't start the client with SASL_SUCCESS_DATA so we may + // need to send one more "empty" message to the server. + AuthenticationStepMessage message; + if (output != NULL && length > 0) { + message.set_data(output, length); + } + reply(message); + } else { + status = ERROR; + std::string error(sasl_errdetail(connection)); + promise.fail("Failed to perform authentication step: " + error); + } + } + + void completed() + { + if (status != STEPPING) { + status = ERROR; + promise.fail("Unexpected authentication 'completed' received"); + return; + } + + LOG(INFO) << "Authentication success"; + + status = COMPLETED; + promise.set(true); + } + + void failed() + { + status = FAILED; + promise.set(false); + } + + void error(const std::string& error) + { + status = ERROR; + promise.fail("Authentication error: " + error); + } + + void discarded() + { + status = DISCARDED; + promise.fail("Authentication discarded"); + } + +private: + static int user( + void* context, + int id, + const char** result, + unsigned* length) + { + CHECK(SASL_CB_USER == id || SASL_CB_AUTHNAME == id); + *result = static_cast<const char*>(context); + if (length != NULL) { + *length = strlen(*result); + } + return SASL_OK; + } + + static int pass( + sasl_conn_t* connection, + void* context, + int id, + sasl_secret_t** secret) + { + CHECK_EQ(SASL_CB_PASS, id); + *secret = static_cast<sasl_secret_t*>(context); + return SASL_OK; + } + + const Credential credential; + + // PID of the client that needs to be authenticated. + const process::UPID client; + + sasl_secret_t* secret; + + sasl_callback_t callbacks[5]; + + enum { + READY, + STARTING, + STEPPING, + COMPLETED, + FAILED, + ERROR, + DISCARDED + } status; + + sasl_conn_t* connection; + + process::Promise<bool> promise; +}; + + +inline Authenticatee::Authenticatee( + const Credential& credential, + const process::UPID& client) +{ + process = new AuthenticateeProcess(credential, client); + process::spawn(process); +} + + +inline Authenticatee::~Authenticatee() +{ + process::terminate(process); + process::wait(process); + delete process; +} + + +inline process::Future<bool> Authenticatee::authenticate( + const process::UPID& pid) +{ + return process::dispatch(process, &AuthenticateeProcess::authenticate, pid); +} + +} // namespace cram_md5 { +} // namespace internal { +} // namespace mesos { + +#endif //__AUTHENTICATION_CRAM_MD5_AUTHENTICATEE_HPP__ http://git-wip-us.apache.org/repos/asf/mesos/blob/b51f5550/src/authentication/cram_md5/authenticator.hpp ---------------------------------------------------------------------- diff --git a/src/authentication/cram_md5/authenticator.hpp b/src/authentication/cram_md5/authenticator.hpp new file mode 100644 index 0000000..7953418 --- /dev/null +++ b/src/authentication/cram_md5/authenticator.hpp @@ -0,0 +1,494 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __AUTHENTICATION_CRAM_MD5_AUTHENTICATOR_HPP__ +#define __AUTHENTICATION_CRAM_MD5_AUTHENTICATOR_HPP__ + +#include <sasl/sasl.h> +#include <sasl/saslplug.h> + +#include <string> +#include <vector> + +#include <mesos/mesos.hpp> + +#include <process/defer.hpp> +#include <process/future.hpp> +#include <process/id.hpp> +#include <process/once.hpp> +#include <process/process.hpp> +#include <process/protobuf.hpp> + +#include <stout/check.hpp> + +#include "authentication/cram_md5/auxprop.hpp" + +#include "messages/messages.hpp" + +namespace mesos { +namespace internal { +namespace cram_md5 { + +// Forward declaration. +class AuthenticatorProcess; + + +class Authenticator +{ +public: + explicit Authenticator(const process::UPID& pid); + ~Authenticator(); + + // Returns the principal of the Authenticatee if successfully + // authenticated otherwise None or an error. Note that we + // distinguish authentication failure (None) from a failed future + // in the event the future failed due to a transient error and + // authentication can (should) be retried. Discarding the future + // will cause the future to fail if it hasn't already completed + // since we have already started the authentication procedure and + // can't reliably cancel. + process::Future<Option<std::string> > authenticate(); + +private: + AuthenticatorProcess* process; +}; + + +class AuthenticatorProcess : public ProtobufProcess<AuthenticatorProcess> +{ +public: + explicit AuthenticatorProcess(const process::UPID& _pid) + : ProcessBase(process::ID::generate("authenticator")), + status(READY), + pid(_pid), + connection(NULL) {} + + virtual ~AuthenticatorProcess() + { + if (connection != NULL) { + sasl_dispose(&connection); + } + } + + virtual void finalize() + { + discarded(); // Fail the promise. + } + + process::Future<Option<std::string> > authenticate() + { + static process::Once* initialize = new process::Once(); + static bool initialized = false; + + if (!initialize->once()) { + LOG(INFO) << "Initializing server SASL"; + + int result = sasl_server_init(NULL, "mesos"); + + if (result != SASL_OK) { + std::string error = "Failed to initialize SASL: "; + error += sasl_errstring(result, NULL, NULL); + LOG(ERROR) << error; + AuthenticationErrorMessage message; + message.set_error(error); + send(pid, message); + status = ERROR; + promise.fail(error); + initialize->done(); + return promise.future(); + } + + result = sasl_auxprop_add_plugin( + InMemoryAuxiliaryPropertyPlugin::name(), + &InMemoryAuxiliaryPropertyPlugin::initialize); + + if (result != SASL_OK) { + std::string error = + "Failed to add \"in-memory\" auxiliary property plugin: "; + error += sasl_errstring(result, NULL, NULL); + LOG(ERROR) << error; + AuthenticationErrorMessage message; + message.set_error(error); + send(pid, message); + status = ERROR; + promise.fail(error); + initialize->done(); + return promise.future(); + } + + initialized = true; + + initialize->done(); + } + + if (!initialized) { + promise.fail("Failed to initialize SASL"); + return promise.future(); + } + + if (status != READY) { + return promise.future(); + } + + callbacks[0].id = SASL_CB_GETOPT; + callbacks[0].proc = (int(*)()) &getopt; + callbacks[0].context = NULL; + + callbacks[1].id = SASL_CB_CANON_USER; + callbacks[1].proc = (int(*)()) &canonicalize; + // Pass in the principal so we can set it in canon_user(). + callbacks[1].context = &principal; + + callbacks[2].id = SASL_CB_LIST_END; + callbacks[2].proc = NULL; + callbacks[2].context = NULL; + + LOG(INFO) << "Creating new server SASL connection"; + + int result = sasl_server_new( + "mesos", // Registered name of service. + NULL, // Server's FQDN; NULL uses gethostname(). + NULL, // The user realm used for password lookups; + // NULL means default to FQDN. + // NOTE: This does not affect Kerberos. + NULL, NULL, // IP address information strings. + callbacks, // Callbacks supported only for this connection. + 0, // Security flags (security layers are enabled + // using security properties, separately). + &connection); + + if (result != SASL_OK) { + std::string error = "Failed to create server SASL connection: "; + error += sasl_errstring(result, NULL, NULL); + LOG(ERROR) << error; + AuthenticationErrorMessage message; + message.set_error(error); + send(pid, message); + status = ERROR; + promise.fail(error); + return promise.future(); + } + + // Get the list of mechanisms. + const char* output = NULL; + unsigned length = 0; + int count = 0; + + result = sasl_listmech( + connection, // The context for this connection. + NULL, // Not supported. + "", // What to prepend to the output string. + ",", // What to separate mechanisms with. + "", // What to append to the output string. + &output, // The output string. + &length, // The length of the output string. + &count); // The count of the mechanisms in output. + + if (result != SASL_OK) { + std::string error = "Failed to get list of mechanisms: "; + LOG(WARNING) << error << sasl_errstring(result, NULL, NULL); + AuthenticationErrorMessage message; + error += sasl_errdetail(connection); + message.set_error(error); + send(pid, message); + status = ERROR; + promise.fail(error); + return promise.future(); + } + + std::vector<std::string> mechanisms = strings::tokenize(output, ","); + + // Send authentication mechanisms. + AuthenticationMechanismsMessage message; + foreach (const std::string& mechanism, mechanisms) { + message.add_mechanisms(mechanism); + } + + send(pid, message); + + status = STARTING; + + // Stop authenticating if nobody cares. + promise.future().onDiscard(defer(self(), &Self::discarded)); + + return promise.future(); + } + +protected: + virtual void initialize() + { + link(pid); // Don't bother waiting for a lost authenticatee. + + // Anticipate start and steps messages from the client. + install<AuthenticationStartMessage>( + &AuthenticatorProcess::start, + &AuthenticationStartMessage::mechanism, + &AuthenticationStartMessage::data); + + install<AuthenticationStepMessage>( + &AuthenticatorProcess::step, + &AuthenticationStepMessage::data); + } + + virtual void exited(const process::UPID& _pid) + { + if (pid == _pid) { + status = ERROR; + promise.fail("Failed to communicate with authenticatee"); + } + } + + void start(const std::string& mechanism, const std::string& data) + { + if (status != STARTING) { + AuthenticationErrorMessage message; + message.set_error("Unexpected authentication 'start' received"); + send(pid, message); + status = ERROR; + promise.fail(message.error()); + return; + } + + LOG(INFO) << "Received SASL authentication start"; + + // Start the server. + const char* output = NULL; + unsigned length = 0; + + int result = sasl_server_start( + connection, + mechanism.c_str(), + data.length() == 0 ? NULL : data.data(), + data.length(), + &output, + &length); + + handle(result, output, length); + } + + void step(const std::string& data) + { + if (status != STEPPING) { + AuthenticationErrorMessage message; + message.set_error("Unexpected authentication 'step' received"); + send(pid, message); + status = ERROR; + promise.fail(message.error()); + return; + } + + LOG(INFO) << "Received SASL authentication step"; + + const char* output = NULL; + unsigned length = 0; + + int result = sasl_server_step( + connection, + data.length() == 0 ? NULL : data.data(), + data.length(), + &output, + &length); + + handle(result, output, length); + } + + void discarded() + { + status = DISCARDED; + promise.fail("Authentication discarded"); + } + +private: + static int getopt( + void* context, + const char* plugin, + const char* option, + const char** result, + unsigned* length) + { + bool found = false; + if (std::string(option) == "auxprop_plugin") { + *result = "in-memory-auxprop"; + found = true; + } else if (std::string(option) == "mech_list") { + *result = "CRAM-MD5"; + found = true; + } else if (std::string(option) == "pwcheck_method") { + *result = "auxprop"; + found = true; + } + + if (found && length != NULL) { + *length = strlen(*result); + } + + return SASL_OK; + } + + // Callback for canonicalizing the username (principal). We use it + // to record the principal in Authenticator. + static int canonicalize( + sasl_conn_t* connection, + void* context, + const char* input, + unsigned inputLength, + unsigned flags, + const char* userRealm, + char* output, + unsigned outputMaxLength, + unsigned* outputLength) + { + CHECK_NOTNULL(input); + CHECK_NOTNULL(context); + CHECK_NOTNULL(output); + + // Save the input. + Option<std::string>* principal = + static_cast<Option<std::string>*>(context); + CHECK(principal->isNone()); + *principal = std::string(input, inputLength); + + // Tell SASL that the canonical username is the same as the + // client-supplied username. + memcpy(output, input, inputLength); + *outputLength = inputLength; + + return SASL_OK; + } + + // Helper for handling result of server start and step. + void handle(int result, const char* output, unsigned length) + { + if (result == SASL_OK) { + // Principal must have been set if authentication succeeded. + CHECK_SOME(principal); + + LOG(INFO) << "Authentication success"; + // Note that we're not using SASL_SUCCESS_DATA which means that + // we should not have any data to send when we get a SASL_OK. + CHECK(output == NULL); + send(pid, AuthenticationCompletedMessage()); + status = COMPLETED; + promise.set(principal); + } else if (result == SASL_CONTINUE) { + LOG(INFO) << "Authentication requires more steps"; + AuthenticationStepMessage message; + message.set_data(CHECK_NOTNULL(output), length); + send(pid, message); + status = STEPPING; + } else if (result == SASL_NOUSER || result == SASL_BADAUTH) { + LOG(WARNING) << "Authentication failure: " + << sasl_errstring(result, NULL, NULL); + send(pid, AuthenticationFailedMessage()); + status = FAILED; + promise.set(Option<std::string>::none()); + } else { + LOG(ERROR) << "Authentication error: " + << sasl_errstring(result, NULL, NULL); + AuthenticationErrorMessage message; + std::string error(sasl_errdetail(connection)); + message.set_error(error); + send(pid, message); + status = ERROR; + promise.fail(message.error()); + } + } + + enum { + READY, + STARTING, + STEPPING, + COMPLETED, + FAILED, + ERROR, + DISCARDED + } status; + + sasl_callback_t callbacks[3]; + + const process::UPID pid; + + sasl_conn_t* connection; + + process::Promise<Option<std::string> > promise; + + Option<std::string> principal; +}; + + +Authenticator::Authenticator(const process::UPID& pid) +{ + process = new AuthenticatorProcess(pid); + process::spawn(process); +} + + +Authenticator::~Authenticator() +{ + // TODO(vinod): As a short term fix for the race condition #1 in + // MESOS-1866, we inject the 'terminate' event at the end of the + // AuthenticatorProcess queue instead of at the front. + // The long term fix for this https://reviews.apache.org/r/25945/. + process::terminate(process, false); + + process::wait(process); + delete process; +} + + +process::Future<Option<std::string> > Authenticator::authenticate() +{ + return process::dispatch(process, &AuthenticatorProcess::authenticate); +} + + +namespace secrets { + +// Loads secrets (principal -> secret) into the in-memory auxiliary +// property plugin that is used by the authenticators. +void load(const std::map<std::string, std::string>& secrets) +{ + Multimap<std::string, Property> properties; + + foreachpair (const std::string& principal, + const std::string& secret, secrets) { + Property property; + property.name = SASL_AUX_PASSWORD_PROP; + property.values.push_back(secret); + properties.put(principal, property); + } + + InMemoryAuxiliaryPropertyPlugin::load(properties); +} + +void load(const Credentials& credentials) +{ + std::map<std::string, std::string> secrets; + foreach(const Credential& credential, credentials.credentials()) { + secrets[credential.principal()] = credential.secret(); + } + load(secrets); +} + +} // namespace secrets { + +} // namespace cram_md5 { +} // namespace internal { +} // namespace mesos { + +#endif //__AUTHENTICATION_CRAM_MD5_AUTHENTICATOR_HPP__ http://git-wip-us.apache.org/repos/asf/mesos/blob/b51f5550/src/authentication/cram_md5/auxprop.cpp ---------------------------------------------------------------------- diff --git a/src/authentication/cram_md5/auxprop.cpp b/src/authentication/cram_md5/auxprop.cpp new file mode 100644 index 0000000..cf503a2 --- /dev/null +++ b/src/authentication/cram_md5/auxprop.cpp @@ -0,0 +1,204 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "authentication/cram_md5/auxprop.hpp" + +#include "logging/logging.hpp" + +using std::list; +using std::string; + +namespace mesos { +namespace internal { +namespace cram_md5 { + +// Storage for the static members. +Multimap<string, Property> InMemoryAuxiliaryPropertyPlugin::properties; +sasl_auxprop_plug_t InMemoryAuxiliaryPropertyPlugin::plugin; + + +int InMemoryAuxiliaryPropertyPlugin::initialize( + const sasl_utils_t* utils, + int api, + int* version, + sasl_auxprop_plug_t** plug, + const char* name) +{ + if (version == NULL || plug == NULL) { + return SASL_BADPARAM; + } + + // Check if SASL API is older than the one we were compiled against. + if (api < SASL_AUXPROP_PLUG_VERSION) { + return SASL_BADVERS; + } + + *version = SASL_AUXPROP_PLUG_VERSION; + + plugin.features = 0; + plugin.spare_int1 = 0; + plugin.glob_context = NULL; + plugin.auxprop_free = NULL; + plugin.auxprop_lookup = &InMemoryAuxiliaryPropertyPlugin::lookup; + plugin.name = const_cast<char*>(InMemoryAuxiliaryPropertyPlugin::name()); + plugin.auxprop_store = NULL; + + *plug = &plugin; + + VLOG(1) << "Initialized in-memory auxiliary property plugin"; + + return SASL_OK; +} + + +#if SASL_AUXPROP_PLUG_VERSION <= 4 + void InMemoryAuxiliaryPropertyPlugin::lookup( +#else + int InMemoryAuxiliaryPropertyPlugin::lookup( +#endif + void* context, + sasl_server_params_t* sparams, + unsigned flags, + const char* user, + unsigned length) +{ + // Pull out the utils. + const sasl_utils_t* utils = sparams->utils; + + // We determine the properties we should be looking up by doing a + // 'prop_get' on the property context. Note that some of the + // properties we get might might need to be skipped depending on the + // flags (see below). + const propval* properties = utils->prop_get(sparams->propctx); + + CHECK(properties != NULL) + << "Invalid auxiliary properties requested for lookup"; + + // TODO(benh): Consider "parsing" 'user' if it has an '@' separating + // the actual user and a realm. + + string realm = sparams->user_realm != NULL + ? sparams->user_realm + : sparams->serverFQDN; + + VLOG(1) + << "Request to lookup properties for " + << "user: '" << user << "' " + << "realm: '" << realm << "' " + << "server FQDN: '" << sparams->serverFQDN << "' " +#ifdef SASL_AUXPROP_VERIFY_AGAINST_HASH + << "SASL_AUXPROP_VERIFY_AGAINST_HASH: " + << (flags & SASL_AUXPROP_VERIFY_AGAINST_HASH ? "true ": "false ") +#endif + << "SASL_AUXPROP_OVERRIDE: " + << (flags & SASL_AUXPROP_OVERRIDE ? "true ": "false ") + << "SASL_AUXPROP_AUTHZID: " + << (flags & SASL_AUXPROP_AUTHZID ? "true ": "false "); + + // Now iterate through each property requested. + const propval* property = properties; + for (; property->name != NULL; property++) { + const char* name = property->name; + + // Skip properties that don't apply to this lookup given the flags. + if (flags & SASL_AUXPROP_AUTHZID) { + if (name[0] == '*') { + VLOG(1) << "Skipping auxiliary property '" << name + << "' since SASL_AUXPROP_AUTHZID == true"; + continue; + } + } else { + // Only consider properties that start with '*' if + // SASL_AUXPROP_AUTHZID is not set but don't include the '*' + // when looking up the property name. + if (name[0] != '*') { + VLOG(1) << "Skipping auxiliary property '" << name + << "' since SASL_AUXPROP_AUTHZID == false " + << "but property name starts with '*'"; + continue; + } else { + name = name + 1; + } + } + + // Don't override already set values unless instructed otherwise. + if (property->values != NULL && !(flags & SASL_AUXPROP_OVERRIDE)) { +#ifdef SASL_AUXPROP_VERIFY_AGAINST_HASH + // Regardless of SASL_AUXPROP_OVERRIDE we're expected to + // override property 'userPassword' when the + // SASL_AUXPROP_VERIFY_AGAINST_HASH flag is set, so we erase it + // here. + if (flags & SASL_AUXPROP_VERIFY_AGAINST_HASH && + string(name) == string(SASL_AUX_PASSWORD_PROP)) { + VLOG(1) << "Erasing auxiliary property '" << name + << "' even though SASL_AUXPROP_OVERRIDE == true " + << "since SASL_AUXPROP_VERIFY_AGAINST_HASH == true"; + utils->prop_erase(sparams->propctx, property->name); + } else { + VLOG(1) << "Skipping auxiliary property '" << name + << "' since SASL_AUXPROP_OVERRIDE == false " + << "and value(s) already set"; + continue; + } +#else + VLOG(1) << "Skipping auxiliary property '" << name + << "' since SASL_AUXPROP_OVERRIDE == false " + << "and value(s) already set"; + continue; +#endif + } else if (property->values != NULL) { + CHECK(flags & SASL_AUXPROP_OVERRIDE); + VLOG(1) << "Erasing auxiliary property '" << name + << "' since SASL_AUXPROP_OVERRIDE == true"; + utils->prop_erase(sparams->propctx, property->name); + } + + VLOG(1) << "Looking up auxiliary property '" << property->name << "'"; + + Option<list<string> > values = lookup(user, name); + + if (values.isSome()) { + if (values.get().empty()) { + // Add the 'NULL' value to indicate there were no values. + utils->prop_set(sparams->propctx, property->name, NULL, 0); + } else { + // Add all the values. Note that passing NULL as the property + // name for 'prop_set' will append values to the same name as + // the previous 'prop_set' calls which is the behavior we want + // after adding the first value. + bool append = false; + foreach (const string& value, values.get()) { + sparams->utils->prop_set( + sparams->propctx, + append ? NULL : property->name, + value.c_str(), + -1); // Let 'prop_set' use strlen. + append = true; + } + } + } + } + +#if SASL_AUXPROP_PLUG_VERSION > 4 + return SASL_OK; +#endif +} + +} // namespace cram_md5 { +} // namespace internal { +} // namespace mesos { http://git-wip-us.apache.org/repos/asf/mesos/blob/b51f5550/src/authentication/cram_md5/auxprop.hpp ---------------------------------------------------------------------- diff --git a/src/authentication/cram_md5/auxprop.hpp b/src/authentication/cram_md5/auxprop.hpp new file mode 100644 index 0000000..b894386 --- /dev/null +++ b/src/authentication/cram_md5/auxprop.hpp @@ -0,0 +1,96 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __AUTHENTICATION_CRAM_MD5_AUXPROP_HPP__ +#define __AUTHENTICATION_CRAM_MD5_AUXPROP_HPP__ + +#include <string> + +#include <sasl/sasl.h> +#include <sasl/saslplug.h> + +#include <stout/foreach.hpp> +#include <stout/multimap.hpp> +#include <stout/none.hpp> +#include <stout/option.hpp> + +namespace mesos { +namespace internal { +namespace cram_md5 { + +struct Property +{ + std::string name; + std::list<std::string> values; +}; + + +class InMemoryAuxiliaryPropertyPlugin +{ +public: + static const char* name() { return "in-memory-auxprop"; } + + static void load(const Multimap<std::string, Property>& _properties) + { + properties = _properties; + } + + static Option<std::list<std::string> > lookup( + const std::string& user, + const std::string& name) + { + if (properties.contains(user)) { + foreach (const Property& property, properties.get(user)) { + if (property.name == name) { + return property.values; + } + } + } + return None(); + } + + // SASL plugin initialize entry. + static int initialize( + const sasl_utils_t* utils, + int api, + int* version, + sasl_auxprop_plug_t** plug, + const char* name); + +private: +#if SASL_AUXPROP_PLUG_VERSION <= 4 + static void lookup( +#else + static int lookup( +#endif + void* context, + sasl_server_params_t* sparams, + unsigned flags, + const char* user, + unsigned length); + + static Multimap<std::string, Property> properties; + + static sasl_auxprop_plug_t plugin; +}; + +} // namespace cram_md5 { +} // namespace internal { +} // namespace mesos { + +#endif // __AUTHENTICATION_CRAM_MD5_AUXPROP_HPP__ http://git-wip-us.apache.org/repos/asf/mesos/blob/b51f5550/src/master/master.cpp ---------------------------------------------------------------------- diff --git a/src/master/master.cpp b/src/master/master.cpp index 762d2ff..39e27c5 100644 --- a/src/master/master.cpp +++ b/src/master/master.cpp @@ -51,9 +51,9 @@ #include <stout/utils.hpp> #include <stout/uuid.hpp> -#include "authorizer/authorizer.hpp" +#include "authentication/cram_md5/authenticator.hpp" -#include "sasl/authenticator.hpp" +#include "authorizer/authorizer.hpp" #include "common/build.hpp" #include "common/date_utils.hpp" @@ -380,8 +380,8 @@ void Master::initialize() // Store credentials in master to use them in routes. credentials = _credentials.get(); - // Load "registration" credentials into SASL based Authenticator. - sasl::secrets::load(_credentials.get()); + // Load "registration" credentials into CRAM-MD5 Authenticator. + cram_md5::secrets::load(_credentials.get()); } else if (flags.authenticate_frameworks || flags.authenticate_slaves) { EXIT(1) << "Authentication requires a credentials file" @@ -3857,7 +3857,8 @@ void Master::authenticate(const UPID& from, const UPID& pid) Owned<Promise<Nothing> > promise(new Promise<Nothing>()); // Create the authenticator. - Owned<sasl::Authenticator> authenticator(new sasl::Authenticator(from)); + Owned<cram_md5::Authenticator> authenticator( + new cram_md5::Authenticator(from)); // Start authentication. const Future<Option<string> >& future = authenticator->authenticate() http://git-wip-us.apache.org/repos/asf/mesos/blob/b51f5550/src/master/master.hpp ---------------------------------------------------------------------- diff --git a/src/master/master.hpp b/src/master/master.hpp index b1a2cd0..468b6e1 100644 --- a/src/master/master.hpp +++ b/src/master/master.hpp @@ -72,7 +72,7 @@ namespace registry { class Slaves; } -namespace sasl { +namespace cram_md5 { class Authenticator; } @@ -584,7 +584,8 @@ private: // authenticated. hashmap<process::UPID, process::Future<Nothing> > authenticating; - hashmap<process::UPID, process::Owned<sasl::Authenticator> > authenticators; + hashmap<process::UPID, process::Owned<cram_md5::Authenticator> > + authenticators; // Principals of authenticated frameworks/slaves keyed by PID. hashmap<process::UPID, std::string> authenticated; http://git-wip-us.apache.org/repos/asf/mesos/blob/b51f5550/src/sasl/authenticatee.hpp ---------------------------------------------------------------------- diff --git a/src/sasl/authenticatee.hpp b/src/sasl/authenticatee.hpp deleted file mode 100644 index ec2c841..0000000 --- a/src/sasl/authenticatee.hpp +++ /dev/null @@ -1,412 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef __SASL_AUTHENTICATEE_HPP__ -#define __SASL_AUTHENTICATEE_HPP__ - -#include <sasl/sasl.h> - -#include <mesos/mesos.hpp> - -#include <process/defer.hpp> -#include <process/future.hpp> -#include <process/id.hpp> -#include <process/once.hpp> -#include <process/process.hpp> -#include <process/protobuf.hpp> - -#include <stout/strings.hpp> - -#include "messages/messages.hpp" - -namespace mesos { -namespace internal { -namespace sasl { - -// Forward declaration. -class AuthenticateeProcess; - - -class Authenticatee -{ -public: - // 'credential' is used to authenticate the 'client'. - Authenticatee(const Credential& credential, const process::UPID& client); - ~Authenticatee(); - - // Returns true if successfully authenticated otherwise false or an - // error. Note that we distinguish authentication failure (false) - // from a failed future in the event the future failed due to a - // transient error and authentication can (should) be - // retried. Discarding the future will cause the future to fail if - // it hasn't already completed since we have already started the - // authentication procedure and can't reliably cancel. - process::Future<bool> authenticate(const process::UPID& pid); - -private: - AuthenticateeProcess* process; -}; - - -class AuthenticateeProcess : public ProtobufProcess<AuthenticateeProcess> -{ -public: - AuthenticateeProcess(const Credential& _credential, - const process::UPID& _client) - : ProcessBase(process::ID::generate("authenticatee")), - credential(_credential), - client(_client), - status(READY), - connection(NULL) - { - const char* data = credential.secret().data(); - size_t length = credential.secret().length(); - - // Need to allocate the secret via 'malloc' because SASL is - // expecting the data appended to the end of the struct. *sigh* - secret = (sasl_secret_t*) malloc(sizeof(sasl_secret_t) + length); - - CHECK(secret != NULL) << "Failed to allocate memory for secret"; - - memcpy(secret->data, data, length); - secret->len = length; - } - - virtual ~AuthenticateeProcess() - { - if (connection != NULL) { - sasl_dispose(&connection); - } - free(secret); - } - - virtual void finalize() - { - discarded(); // Fail the promise. - } - - process::Future<bool> authenticate(const process::UPID& pid) - { - static process::Once* initialize = new process::Once(); - static bool initialized = false; - - if (!initialize->once()) { - LOG(INFO) << "Initializing client SASL"; - int result = sasl_client_init(NULL); - if (result != SASL_OK) { - status = ERROR; - std::string error(sasl_errstring(result, NULL, NULL)); - promise.fail("Failed to initialize SASL: " + error); - initialize->done(); - return promise.future(); - } - - initialized = true; - - initialize->done(); - } - - if (!initialized) { - promise.fail("Failed to initialize SASL"); - return promise.future(); - } - - if (status != READY) { - return promise.future(); - } - - LOG(INFO) << "Creating new client SASL connection"; - - callbacks[0].id = SASL_CB_GETREALM; - callbacks[0].proc = NULL; - callbacks[0].context = NULL; - - callbacks[1].id = SASL_CB_USER; - callbacks[1].proc = (int(*)()) &user; - callbacks[2].context = (void*) credential.principal().c_str(); - - // NOTE: Some SASL mechanisms do not allow/enable "proxying", - // i.e., authorization. Therefore, some mechanisms send _only_ the - // authoriation name rather than both the user (authentication - // name) and authorization name. Thus, for now, we assume - // authorization is handled out of band. Consider the - // SASL_NEED_PROXY flag if we want to reconsider this in the - // future. - callbacks[2].id = SASL_CB_AUTHNAME; - callbacks[2].proc = (int(*)()) &user; - callbacks[2].context = (void*) credential.principal().c_str(); - - callbacks[3].id = SASL_CB_PASS; - callbacks[3].proc = (int(*)()) &pass; - callbacks[3].context = (void*) secret; - - callbacks[4].id = SASL_CB_LIST_END; - callbacks[4].proc = NULL; - callbacks[4].context = NULL; - - int result = sasl_client_new( - "mesos", // Registered name of service. - NULL, // Server's FQDN. - NULL, NULL, // IP Address information strings. - callbacks, // Callbacks supported only for this connection. - 0, // Security flags (security layers are enabled - // using security properties, separately). - &connection); - - if (result != SASL_OK) { - status = ERROR; - std::string error(sasl_errstring(result, NULL, NULL)); - promise.fail("Failed to create client SASL connection: " + error); - return promise.future(); - } - - AuthenticateMessage message; - message.set_pid(client); - send(pid, message); - - status = STARTING; - - // Stop authenticating if nobody cares. - promise.future().onDiscard(defer(self(), &Self::discarded)); - - return promise.future(); - } - -protected: - virtual void initialize() - { - // Anticipate mechanisms and steps from the server. - install<AuthenticationMechanismsMessage>( - &AuthenticateeProcess::mechanisms, - &AuthenticationMechanismsMessage::mechanisms); - - install<AuthenticationStepMessage>( - &AuthenticateeProcess::step, - &AuthenticationStepMessage::data); - - install<AuthenticationCompletedMessage>( - &AuthenticateeProcess::completed); - - install<AuthenticationFailedMessage>( - &AuthenticateeProcess::failed); - - install<AuthenticationErrorMessage>( - &AuthenticateeProcess::error, - &AuthenticationErrorMessage::error); - } - - void mechanisms(const std::vector<std::string>& mechanisms) - { - if (status != STARTING) { - status = ERROR; - promise.fail("Unexpected authentication 'mechanisms' received"); - return; - } - - // TODO(benh): Store 'from' in order to ensure we only communicate - // with the same Authenticator. - - LOG(INFO) << "Received SASL authentication mechanisms: " - << strings::join(",", mechanisms); - - sasl_interact_t* interact = NULL; - const char* output = NULL; - unsigned length = 0; - const char* mechanism = NULL; - - int result = sasl_client_start( - connection, - strings::join(" ", mechanisms).c_str(), - &interact, // Set if an interaction is needed. - &output, // The output string (to send to server). - &length, // The length of the output string. - &mechanism); // The chosen mechanism. - - CHECK_NE(SASL_INTERACT, result) - << "Not expecting an interaction (ID: " << interact->id << ")"; - - if (result != SASL_OK && result != SASL_CONTINUE) { - std::string error(sasl_errdetail(connection)); - status = ERROR; - promise.fail("Failed to start the SASL client: " + error); - return; - } - - LOG(INFO) << "Attempting to authenticate with mechanism '" - << mechanism << "'"; - - AuthenticationStartMessage message; - message.set_mechanism(mechanism); - message.set_data(output, length); - - reply(message); - - status = STEPPING; - } - - void step(const std::string& data) - { - if (status != STEPPING) { - status = ERROR; - promise.fail("Unexpected authentication 'step' received"); - return; - } - - LOG(INFO) << "Received SASL authentication step"; - - sasl_interact_t* interact = NULL; - const char* output = NULL; - unsigned length = 0; - - int result = sasl_client_step( - connection, - data.length() == 0 ? NULL : data.data(), - data.length(), - &interact, - &output, - &length); - - CHECK_NE(SASL_INTERACT, result) - << "Not expecting an interaction (ID: " << interact->id << ")"; - - if (result == SASL_OK || result == SASL_CONTINUE) { - // We don't start the client with SASL_SUCCESS_DATA so we may - // need to send one more "empty" message to the server. - AuthenticationStepMessage message; - if (output != NULL && length > 0) { - message.set_data(output, length); - } - reply(message); - } else { - status = ERROR; - std::string error(sasl_errdetail(connection)); - promise.fail("Failed to perform authentication step: " + error); - } - } - - void completed() - { - if (status != STEPPING) { - status = ERROR; - promise.fail("Unexpected authentication 'completed' received"); - return; - } - - LOG(INFO) << "Authentication success"; - - status = COMPLETED; - promise.set(true); - } - - void failed() - { - status = FAILED; - promise.set(false); - } - - void error(const std::string& error) - { - status = ERROR; - promise.fail("Authentication error: " + error); - } - - void discarded() - { - status = DISCARDED; - promise.fail("Authentication discarded"); - } - -private: - static int user( - void* context, - int id, - const char** result, - unsigned* length) - { - CHECK(SASL_CB_USER == id || SASL_CB_AUTHNAME == id); - *result = static_cast<const char*>(context); - if (length != NULL) { - *length = strlen(*result); - } - return SASL_OK; - } - - static int pass( - sasl_conn_t* connection, - void* context, - int id, - sasl_secret_t** secret) - { - CHECK_EQ(SASL_CB_PASS, id); - *secret = static_cast<sasl_secret_t*>(context); - return SASL_OK; - } - - const Credential credential; - - // PID of the client that needs to be authenticated. - const process::UPID client; - - sasl_secret_t* secret; - - sasl_callback_t callbacks[5]; - - enum { - READY, - STARTING, - STEPPING, - COMPLETED, - FAILED, - ERROR, - DISCARDED - } status; - - sasl_conn_t* connection; - - process::Promise<bool> promise; -}; - - -inline Authenticatee::Authenticatee( - const Credential& credential, - const process::UPID& client) -{ - process = new AuthenticateeProcess(credential, client); - process::spawn(process); -} - - -inline Authenticatee::~Authenticatee() -{ - process::terminate(process); - process::wait(process); - delete process; -} - - -inline process::Future<bool> Authenticatee::authenticate( - const process::UPID& pid) -{ - return process::dispatch(process, &AuthenticateeProcess::authenticate, pid); -} - -} // namespace sasl { -} // namespace internal { -} // namespace mesos { - -#endif //__SASL_AUTHENTICATEE_HPP__ http://git-wip-us.apache.org/repos/asf/mesos/blob/b51f5550/src/sasl/authenticator.hpp ---------------------------------------------------------------------- diff --git a/src/sasl/authenticator.hpp b/src/sasl/authenticator.hpp deleted file mode 100644 index 6f4d3db..0000000 --- a/src/sasl/authenticator.hpp +++ /dev/null @@ -1,494 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef __SASL_AUTHENTICATOR_HPP__ -#define __SASL_AUTHENTICATOR_HPP__ - -#include <sasl/sasl.h> -#include <sasl/saslplug.h> - -#include <string> -#include <vector> - -#include <mesos/mesos.hpp> - -#include <process/defer.hpp> -#include <process/future.hpp> -#include <process/id.hpp> -#include <process/once.hpp> -#include <process/process.hpp> -#include <process/protobuf.hpp> - -#include <stout/check.hpp> - -#include "messages/messages.hpp" - -#include "sasl/auxprop.hpp" - -namespace mesos { -namespace internal { -namespace sasl { - -// Forward declaration. -class AuthenticatorProcess; - - -class Authenticator -{ -public: - explicit Authenticator(const process::UPID& pid); - ~Authenticator(); - - // Returns the principal of the Authenticatee if successfully - // authenticated otherwise None or an error. Note that we - // distinguish authentication failure (None) from a failed future - // in the event the future failed due to a transient error and - // authentication can (should) be retried. Discarding the future - // will cause the future to fail if it hasn't already completed - // since we have already started the authentication procedure and - // can't reliably cancel. - process::Future<Option<std::string> > authenticate(); - -private: - AuthenticatorProcess* process; -}; - - -class AuthenticatorProcess : public ProtobufProcess<AuthenticatorProcess> -{ -public: - explicit AuthenticatorProcess(const process::UPID& _pid) - : ProcessBase(process::ID::generate("authenticator")), - status(READY), - pid(_pid), - connection(NULL) {} - - virtual ~AuthenticatorProcess() - { - if (connection != NULL) { - sasl_dispose(&connection); - } - } - - virtual void finalize() - { - discarded(); // Fail the promise. - } - - process::Future<Option<std::string> > authenticate() - { - static process::Once* initialize = new process::Once(); - static bool initialized = false; - - if (!initialize->once()) { - LOG(INFO) << "Initializing server SASL"; - - int result = sasl_server_init(NULL, "mesos"); - - if (result != SASL_OK) { - std::string error = "Failed to initialize SASL: "; - error += sasl_errstring(result, NULL, NULL); - LOG(ERROR) << error; - AuthenticationErrorMessage message; - message.set_error(error); - send(pid, message); - status = ERROR; - promise.fail(error); - initialize->done(); - return promise.future(); - } - - result = sasl_auxprop_add_plugin( - InMemoryAuxiliaryPropertyPlugin::name(), - &InMemoryAuxiliaryPropertyPlugin::initialize); - - if (result != SASL_OK) { - std::string error = - "Failed to add \"in-memory\" auxiliary property plugin: "; - error += sasl_errstring(result, NULL, NULL); - LOG(ERROR) << error; - AuthenticationErrorMessage message; - message.set_error(error); - send(pid, message); - status = ERROR; - promise.fail(error); - initialize->done(); - return promise.future(); - } - - initialized = true; - - initialize->done(); - } - - if (!initialized) { - promise.fail("Failed to initialize SASL"); - return promise.future(); - } - - if (status != READY) { - return promise.future(); - } - - callbacks[0].id = SASL_CB_GETOPT; - callbacks[0].proc = (int(*)()) &getopt; - callbacks[0].context = NULL; - - callbacks[1].id = SASL_CB_CANON_USER; - callbacks[1].proc = (int(*)()) &canonicalize; - // Pass in the principal so we can set it in canon_user(). - callbacks[1].context = &principal; - - callbacks[2].id = SASL_CB_LIST_END; - callbacks[2].proc = NULL; - callbacks[2].context = NULL; - - LOG(INFO) << "Creating new server SASL connection"; - - int result = sasl_server_new( - "mesos", // Registered name of service. - NULL, // Server's FQDN; NULL uses gethostname(). - NULL, // The user realm used for password lookups; - // NULL means default to FQDN. - // NOTE: This does not affect Kerberos. - NULL, NULL, // IP address information strings. - callbacks, // Callbacks supported only for this connection. - 0, // Security flags (security layers are enabled - // using security properties, separately). - &connection); - - if (result != SASL_OK) { - std::string error = "Failed to create server SASL connection: "; - error += sasl_errstring(result, NULL, NULL); - LOG(ERROR) << error; - AuthenticationErrorMessage message; - message.set_error(error); - send(pid, message); - status = ERROR; - promise.fail(error); - return promise.future(); - } - - // Get the list of mechanisms. - const char* output = NULL; - unsigned length = 0; - int count = 0; - - result = sasl_listmech( - connection, // The context for this connection. - NULL, // Not supported. - "", // What to prepend to the output string. - ",", // What to separate mechanisms with. - "", // What to append to the output string. - &output, // The output string. - &length, // The length of the output string. - &count); // The count of the mechanisms in output. - - if (result != SASL_OK) { - std::string error = "Failed to get list of mechanisms: "; - LOG(WARNING) << error << sasl_errstring(result, NULL, NULL); - AuthenticationErrorMessage message; - error += sasl_errdetail(connection); - message.set_error(error); - send(pid, message); - status = ERROR; - promise.fail(error); - return promise.future(); - } - - std::vector<std::string> mechanisms = strings::tokenize(output, ","); - - // Send authentication mechanisms. - AuthenticationMechanismsMessage message; - foreach (const std::string& mechanism, mechanisms) { - message.add_mechanisms(mechanism); - } - - send(pid, message); - - status = STARTING; - - // Stop authenticating if nobody cares. - promise.future().onDiscard(defer(self(), &Self::discarded)); - - return promise.future(); - } - -protected: - virtual void initialize() - { - link(pid); // Don't bother waiting for a lost authenticatee. - - // Anticipate start and steps messages from the client. - install<AuthenticationStartMessage>( - &AuthenticatorProcess::start, - &AuthenticationStartMessage::mechanism, - &AuthenticationStartMessage::data); - - install<AuthenticationStepMessage>( - &AuthenticatorProcess::step, - &AuthenticationStepMessage::data); - } - - virtual void exited(const process::UPID& _pid) - { - if (pid == _pid) { - status = ERROR; - promise.fail("Failed to communicate with authenticatee"); - } - } - - void start(const std::string& mechanism, const std::string& data) - { - if (status != STARTING) { - AuthenticationErrorMessage message; - message.set_error("Unexpected authentication 'start' received"); - send(pid, message); - status = ERROR; - promise.fail(message.error()); - return; - } - - LOG(INFO) << "Received SASL authentication start"; - - // Start the server. - const char* output = NULL; - unsigned length = 0; - - int result = sasl_server_start( - connection, - mechanism.c_str(), - data.length() == 0 ? NULL : data.data(), - data.length(), - &output, - &length); - - handle(result, output, length); - } - - void step(const std::string& data) - { - if (status != STEPPING) { - AuthenticationErrorMessage message; - message.set_error("Unexpected authentication 'step' received"); - send(pid, message); - status = ERROR; - promise.fail(message.error()); - return; - } - - LOG(INFO) << "Received SASL authentication step"; - - const char* output = NULL; - unsigned length = 0; - - int result = sasl_server_step( - connection, - data.length() == 0 ? NULL : data.data(), - data.length(), - &output, - &length); - - handle(result, output, length); - } - - void discarded() - { - status = DISCARDED; - promise.fail("Authentication discarded"); - } - -private: - static int getopt( - void* context, - const char* plugin, - const char* option, - const char** result, - unsigned* length) - { - bool found = false; - if (std::string(option) == "auxprop_plugin") { - *result = "in-memory-auxprop"; - found = true; - } else if (std::string(option) == "mech_list") { - *result = "CRAM-MD5"; - found = true; - } else if (std::string(option) == "pwcheck_method") { - *result = "auxprop"; - found = true; - } - - if (found && length != NULL) { - *length = strlen(*result); - } - - return SASL_OK; - } - - // Callback for canonicalizing the username (principal). We use it - // to record the principal in Authenticator. - static int canonicalize( - sasl_conn_t* connection, - void* context, - const char* input, - unsigned inputLength, - unsigned flags, - const char* userRealm, - char* output, - unsigned outputMaxLength, - unsigned* outputLength) - { - CHECK_NOTNULL(input); - CHECK_NOTNULL(context); - CHECK_NOTNULL(output); - - // Save the input. - Option<std::string>* principal = - static_cast<Option<std::string>*>(context); - CHECK(principal->isNone()); - *principal = std::string(input, inputLength); - - // Tell SASL that the canonical username is the same as the - // client-supplied username. - memcpy(output, input, inputLength); - *outputLength = inputLength; - - return SASL_OK; - } - - // Helper for handling result of server start and step. - void handle(int result, const char* output, unsigned length) - { - if (result == SASL_OK) { - // Principal must have been set if authentication succeeded. - CHECK_SOME(principal); - - LOG(INFO) << "Authentication success"; - // Note that we're not using SASL_SUCCESS_DATA which means that - // we should not have any data to send when we get a SASL_OK. - CHECK(output == NULL); - send(pid, AuthenticationCompletedMessage()); - status = COMPLETED; - promise.set(principal); - } else if (result == SASL_CONTINUE) { - LOG(INFO) << "Authentication requires more steps"; - AuthenticationStepMessage message; - message.set_data(CHECK_NOTNULL(output), length); - send(pid, message); - status = STEPPING; - } else if (result == SASL_NOUSER || result == SASL_BADAUTH) { - LOG(WARNING) << "Authentication failure: " - << sasl_errstring(result, NULL, NULL); - send(pid, AuthenticationFailedMessage()); - status = FAILED; - promise.set(Option<std::string>::none()); - } else { - LOG(ERROR) << "Authentication error: " - << sasl_errstring(result, NULL, NULL); - AuthenticationErrorMessage message; - std::string error(sasl_errdetail(connection)); - message.set_error(error); - send(pid, message); - status = ERROR; - promise.fail(message.error()); - } - } - - enum { - READY, - STARTING, - STEPPING, - COMPLETED, - FAILED, - ERROR, - DISCARDED - } status; - - sasl_callback_t callbacks[3]; - - const process::UPID pid; - - sasl_conn_t* connection; - - process::Promise<Option<std::string> > promise; - - Option<std::string> principal; -}; - - -Authenticator::Authenticator(const process::UPID& pid) -{ - process = new AuthenticatorProcess(pid); - process::spawn(process); -} - - -Authenticator::~Authenticator() -{ - // TODO(vinod): As a short term fix for the race condition #1 in - // MESOS-1866, we inject the 'terminate' event at the end of the - // AuthenticatorProcess queue instead of at the front. - // The long term fix for this https://reviews.apache.org/r/25945/. - process::terminate(process, false); - - process::wait(process); - delete process; -} - - -process::Future<Option<std::string> > Authenticator::authenticate() -{ - return process::dispatch(process, &AuthenticatorProcess::authenticate); -} - - -namespace secrets { - -// Loads secrets (principal -> secret) into the in-memory auxiliary -// property plugin that is used by the authenticators. -void load(const std::map<std::string, std::string>& secrets) -{ - Multimap<std::string, Property> properties; - - foreachpair (const std::string& principal, - const std::string& secret, secrets) { - Property property; - property.name = SASL_AUX_PASSWORD_PROP; - property.values.push_back(secret); - properties.put(principal, property); - } - - InMemoryAuxiliaryPropertyPlugin::load(properties); -} - -void load(const Credentials& credentials) -{ - std::map<std::string, std::string> secrets; - foreach(const Credential& credential, credentials.credentials()) { - secrets[credential.principal()] = credential.secret(); - } - load(secrets); -} - -} // namespace secrets { - -} // namespace sasl { -} // namespace internal { -} // namespace mesos { - -#endif //__SASL_AUTHENTICATOR_HPP__ http://git-wip-us.apache.org/repos/asf/mesos/blob/b51f5550/src/sasl/auxprop.cpp ---------------------------------------------------------------------- diff --git a/src/sasl/auxprop.cpp b/src/sasl/auxprop.cpp deleted file mode 100644 index 6de1222..0000000 --- a/src/sasl/auxprop.cpp +++ /dev/null @@ -1,186 +0,0 @@ -#include "logging/logging.hpp" - -#include "sasl/auxprop.hpp" - -using std::list; -using std::string; - -namespace mesos { -namespace internal { -namespace sasl { - -// Storage for the static members. -Multimap<string, Property> InMemoryAuxiliaryPropertyPlugin::properties; -sasl_auxprop_plug_t InMemoryAuxiliaryPropertyPlugin::plugin; - - -int InMemoryAuxiliaryPropertyPlugin::initialize( - const sasl_utils_t* utils, - int api, - int* version, - sasl_auxprop_plug_t** plug, - const char* name) -{ - if (version == NULL || plug == NULL) { - return SASL_BADPARAM; - } - - // Check if SASL API is older than the one we were compiled against. - if (api < SASL_AUXPROP_PLUG_VERSION) { - return SASL_BADVERS; - } - - *version = SASL_AUXPROP_PLUG_VERSION; - - plugin.features = 0; - plugin.spare_int1 = 0; - plugin.glob_context = NULL; - plugin.auxprop_free = NULL; - plugin.auxprop_lookup = &InMemoryAuxiliaryPropertyPlugin::lookup; - plugin.name = const_cast<char*>(InMemoryAuxiliaryPropertyPlugin::name()); - plugin.auxprop_store = NULL; - - *plug = &plugin; - - VLOG(1) << "Initialized in-memory auxiliary property plugin"; - - return SASL_OK; -} - - -#if SASL_AUXPROP_PLUG_VERSION <= 4 - void InMemoryAuxiliaryPropertyPlugin::lookup( -#else - int InMemoryAuxiliaryPropertyPlugin::lookup( -#endif - void* context, - sasl_server_params_t* sparams, - unsigned flags, - const char* user, - unsigned length) -{ - // Pull out the utils. - const sasl_utils_t* utils = sparams->utils; - - // We determine the properties we should be looking up by doing a - // 'prop_get' on the property context. Note that some of the - // properties we get might might need to be skipped depending on the - // flags (see below). - const propval* properties = utils->prop_get(sparams->propctx); - - CHECK(properties != NULL) - << "Invalid auxiliary properties requested for lookup"; - - // TODO(benh): Consider "parsing" 'user' if it has an '@' separating - // the actual user and a realm. - - string realm = sparams->user_realm != NULL - ? sparams->user_realm - : sparams->serverFQDN; - - VLOG(1) - << "Request to lookup properties for " - << "user: '" << user << "' " - << "realm: '" << realm << "' " - << "server FQDN: '" << sparams->serverFQDN << "' " -#ifdef SASL_AUXPROP_VERIFY_AGAINST_HASH - << "SASL_AUXPROP_VERIFY_AGAINST_HASH: " - << (flags & SASL_AUXPROP_VERIFY_AGAINST_HASH ? "true ": "false ") -#endif - << "SASL_AUXPROP_OVERRIDE: " - << (flags & SASL_AUXPROP_OVERRIDE ? "true ": "false ") - << "SASL_AUXPROP_AUTHZID: " - << (flags & SASL_AUXPROP_AUTHZID ? "true ": "false "); - - // Now iterate through each property requested. - const propval* property = properties; - for (; property->name != NULL; property++) { - const char* name = property->name; - - // Skip properties that don't apply to this lookup given the flags. - if (flags & SASL_AUXPROP_AUTHZID) { - if (name[0] == '*') { - VLOG(1) << "Skipping auxiliary property '" << name - << "' since SASL_AUXPROP_AUTHZID == true"; - continue; - } - } else { - // Only consider properties that start with '*' if - // SASL_AUXPROP_AUTHZID is not set but don't include the '*' - // when looking up the property name. - if (name[0] != '*') { - VLOG(1) << "Skipping auxiliary property '" << name - << "' since SASL_AUXPROP_AUTHZID == false " - << "but property name starts with '*'"; - continue; - } else { - name = name + 1; - } - } - - // Don't override already set values unless instructed otherwise. - if (property->values != NULL && !(flags & SASL_AUXPROP_OVERRIDE)) { -#ifdef SASL_AUXPROP_VERIFY_AGAINST_HASH - // Regardless of SASL_AUXPROP_OVERRIDE we're expected to - // override property 'userPassword' when the - // SASL_AUXPROP_VERIFY_AGAINST_HASH flag is set, so we erase it - // here. - if (flags & SASL_AUXPROP_VERIFY_AGAINST_HASH && - string(name) == string(SASL_AUX_PASSWORD_PROP)) { - VLOG(1) << "Erasing auxiliary property '" << name - << "' even though SASL_AUXPROP_OVERRIDE == true " - << "since SASL_AUXPROP_VERIFY_AGAINST_HASH == true"; - utils->prop_erase(sparams->propctx, property->name); - } else { - VLOG(1) << "Skipping auxiliary property '" << name - << "' since SASL_AUXPROP_OVERRIDE == false " - << "and value(s) already set"; - continue; - } -#else - VLOG(1) << "Skipping auxiliary property '" << name - << "' since SASL_AUXPROP_OVERRIDE == false " - << "and value(s) already set"; - continue; -#endif - } else if (property->values != NULL) { - CHECK(flags & SASL_AUXPROP_OVERRIDE); - VLOG(1) << "Erasing auxiliary property '" << name - << "' since SASL_AUXPROP_OVERRIDE == true"; - utils->prop_erase(sparams->propctx, property->name); - } - - VLOG(1) << "Looking up auxiliary property '" << property->name << "'"; - - Option<list<string> > values = lookup(user, name); - - if (values.isSome()) { - if (values.get().empty()) { - // Add the 'NULL' value to indicate there were no values. - utils->prop_set(sparams->propctx, property->name, NULL, 0); - } else { - // Add all the values. Note that passing NULL as the property - // name for 'prop_set' will append values to the same name as - // the previous 'prop_set' calls which is the behavior we want - // after adding the first value. - bool append = false; - foreach (const string& value, values.get()) { - sparams->utils->prop_set( - sparams->propctx, - append ? NULL : property->name, - value.c_str(), - -1); // Let 'prop_set' use strlen. - append = true; - } - } - } - } - -#if SASL_AUXPROP_PLUG_VERSION > 4 - return SASL_OK; -#endif -} - -} // namespace sasl { -} // namespace internal { -} // namespace mesos { http://git-wip-us.apache.org/repos/asf/mesos/blob/b51f5550/src/sasl/auxprop.hpp ---------------------------------------------------------------------- diff --git a/src/sasl/auxprop.hpp b/src/sasl/auxprop.hpp deleted file mode 100644 index 44e201b..0000000 --- a/src/sasl/auxprop.hpp +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef __SASL_AUXPROP_HPP__ -#define __SASL_AUXPROP_HPP__ - -#include <sasl/sasl.h> -#include <sasl/saslplug.h> - -#include <string> - -#include <stout/foreach.hpp> -#include <stout/multimap.hpp> -#include <stout/none.hpp> -#include <stout/option.hpp> - -namespace mesos { -namespace internal { -namespace sasl { - -struct Property -{ - std::string name; - std::list<std::string> values; -}; - - -class InMemoryAuxiliaryPropertyPlugin -{ -public: - static const char* name() { return "in-memory-auxprop"; } - - static void load(const Multimap<std::string, Property>& _properties) - { - properties = _properties; - } - - static Option<std::list<std::string> > lookup( - const std::string& user, - const std::string& name) - { - if (properties.contains(user)) { - foreach (const Property& property, properties.get(user)) { - if (property.name == name) { - return property.values; - } - } - } - return None(); - } - - // SASL plugin initialize entry. - static int initialize( - const sasl_utils_t* utils, - int api, - int* version, - sasl_auxprop_plug_t** plug, - const char* name); - -private: -#if SASL_AUXPROP_PLUG_VERSION <= 4 - static void lookup( -#else - static int lookup( -#endif - void* context, - sasl_server_params_t* sparams, - unsigned flags, - const char* user, - unsigned length); - - static Multimap<std::string, Property> properties; - - static sasl_auxprop_plug_t plugin; -}; - -} // namespace sasl { -} // namespace internal { -} // namespace mesos { - -#endif // __SASL_AUXPROP_HPP__ http://git-wip-us.apache.org/repos/asf/mesos/blob/b51f5550/src/sched/sched.cpp ---------------------------------------------------------------------- diff --git a/src/sched/sched.cpp b/src/sched/sched.cpp index 0fb8c7b..d84465c 100644 --- a/src/sched/sched.cpp +++ b/src/sched/sched.cpp @@ -59,7 +59,7 @@ #include <stout/utils.hpp> #include <stout/uuid.hpp> -#include "sasl/authenticatee.hpp" +#include "authentication/cram_md5/authenticatee.hpp" #include "common/lock.hpp" #include "common/type_utils.hpp" @@ -285,7 +285,7 @@ protected: CHECK_SOME(credential); CHECK(authenticatee == NULL); - authenticatee = new sasl::Authenticatee(credential.get(), self()); + authenticatee = new cram_md5::Authenticatee(credential.get(), self()); // NOTE: We do not pass 'Owned<Authenticatee>' here because doing // so could make 'AuthenticateeProcess' responsible for deleting @@ -1024,7 +1024,7 @@ private: const Option<Credential> credential; - sasl::Authenticatee* authenticatee; + cram_md5::Authenticatee* authenticatee; // Indicates if an authentication attempt is in progress. Option<Future<bool> > authenticating; http://git-wip-us.apache.org/repos/asf/mesos/blob/b51f5550/src/scheduler/scheduler.cpp ---------------------------------------------------------------------- diff --git a/src/scheduler/scheduler.cpp b/src/scheduler/scheduler.cpp index fb88a3e..c74187c 100644 --- a/src/scheduler/scheduler.cpp +++ b/src/scheduler/scheduler.cpp @@ -53,7 +53,7 @@ #include <stout/os.hpp> #include <stout/uuid.hpp> -#include "sasl/authenticatee.hpp" +#include "authentication/cram_md5/authenticatee.hpp" #include "common/type_utils.hpp" @@ -465,7 +465,7 @@ protected: CHECK_SOME(credential); CHECK(authenticatee == NULL); - authenticatee = new sasl::Authenticatee(credential.get(), self()); + authenticatee = new cram_md5::Authenticatee(credential.get(), self()); // NOTE: We do not pass 'Owned<Authenticatee>' here because doing // so could make 'AuthenticateeProcess' responsible for deleting @@ -808,7 +808,7 @@ private: Option<UPID> master; - sasl::Authenticatee* authenticatee; + cram_md5::Authenticatee* authenticatee; // Indicates if an authentication attempt is in progress. Option<Future<bool> > authenticating; http://git-wip-us.apache.org/repos/asf/mesos/blob/b51f5550/src/slave/slave.cpp ---------------------------------------------------------------------- diff --git a/src/slave/slave.cpp b/src/slave/slave.cpp index 5e9b0e4..b893517 100644 --- a/src/slave/slave.cpp +++ b/src/slave/slave.cpp @@ -62,6 +62,8 @@ #include "linux/cgroups.hpp" #endif // __linux__ +#include "authentication/cram_md5/authenticatee.hpp" + #include "common/build.hpp" #include "common/protobuf_utils.hpp" #include "common/type_utils.hpp" @@ -71,8 +73,6 @@ #include "logging/logging.hpp" -#include "sasl/authenticatee.hpp" - #include "slave/constants.hpp" #include "slave/flags.hpp" #include "slave/paths.hpp" @@ -667,7 +667,7 @@ void Slave::authenticate() CHECK_SOME(credential); CHECK(authenticatee == NULL); - authenticatee = new sasl::Authenticatee(credential.get(), self()); + authenticatee = new cram_md5::Authenticatee(credential.get(), self()); authenticating = authenticatee->authenticate(master.get()) .onAny(defer(self(), &Self::_authenticate)); http://git-wip-us.apache.org/repos/asf/mesos/blob/b51f5550/src/slave/slave.hpp ---------------------------------------------------------------------- diff --git a/src/slave/slave.hpp b/src/slave/slave.hpp index eb5de73..6c183f8 100644 --- a/src/slave/slave.hpp +++ b/src/slave/slave.hpp @@ -70,9 +70,9 @@ namespace internal { class MasterDetector; // Forward declaration. -namespace sasl { +namespace cram_md5 { class Authenticatee; -} // namespace sasl { +} // namespace cram_md5 { namespace slave { @@ -481,7 +481,7 @@ private: Option<Credential> credential; - sasl::Authenticatee* authenticatee; + cram_md5::Authenticatee* authenticatee; // Indicates if an authentication attempt is in progress. Option<Future<bool> > authenticating; http://git-wip-us.apache.org/repos/asf/mesos/blob/b51f5550/src/tests/cram_md5_authentication_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/cram_md5_authentication_tests.cpp b/src/tests/cram_md5_authentication_tests.cpp new file mode 100644 index 0000000..d27c905 --- /dev/null +++ b/src/tests/cram_md5_authentication_tests.cpp @@ -0,0 +1,209 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <map> +#include <string> + +#include <process/gmock.hpp> +#include <process/gtest.hpp> +#include <process/pid.hpp> +#include <process/process.hpp> + +#include <stout/gtest.hpp> + +#include "authentication/cram_md5/authenticatee.hpp" +#include "authentication/cram_md5/authenticator.hpp" + +#include "tests/mesos.hpp" + +using namespace mesos::internal::tests; + +using namespace process; + +using std::map; +using std::string; + +using testing::_; +using testing::Eq; + +namespace mesos { +namespace internal { +namespace cram_md5 { + +TEST(CRAMMD5Authentication, success) +{ + // Set up secrets. + map<string, string> secrets; + secrets["benh"] = "secret"; + cram_md5::secrets::load(secrets); + + // Launch a dummy process (somebody to send the AuthenticateMessage). + UPID pid = spawn(new ProcessBase(), true); + + Credential credential; + credential.set_principal("benh"); + credential.set_secret("secret"); + + Authenticatee authenticatee(credential, UPID()); + + Future<Message> message = + FUTURE_MESSAGE(Eq(AuthenticateMessage().GetTypeName()), _, _); + + Future<bool> client = authenticatee.authenticate(pid); + + AWAIT_READY(message); + + Authenticator authenticator(message.get().from); + + Future<Option<string> > principal = authenticator.authenticate(); + + AWAIT_EQ(true, client); + AWAIT_READY(principal); + EXPECT_SOME_EQ("benh", principal.get()); + + terminate(pid); +} + + +// Bad password should return an authentication failure. +TEST(CRAMMD5Authentication, failed1) +{ + // Set up secrets. + map<string, string> secrets; + secrets["benh"] = "secret1"; + cram_md5::secrets::load(secrets); + + // Launch a dummy process (somebody to send the AuthenticateMessage). + UPID pid = spawn(new ProcessBase(), true); + + Credential credential; + credential.set_principal("benh"); + credential.set_secret("secret"); + + Authenticatee authenticatee(credential, UPID()); + + Future<Message> message = + FUTURE_MESSAGE(Eq(AuthenticateMessage().GetTypeName()), _, _); + + Future<bool> client = authenticatee.authenticate(pid); + + AWAIT_READY(message); + + Authenticator authenticator(message.get().from); + + Future<Option<string> > server = authenticator.authenticate(); + + AWAIT_EQ(false, client); + AWAIT_READY(server); + EXPECT_NONE(server.get()); + + terminate(pid); +} + + +// No user should return an authentication failure. +TEST(CRAMMD5Authentication, failed2) +{ + // Set up secrets. + map<string, string> secrets; + secrets["vinod"] = "secret"; + cram_md5::secrets::load(secrets); + + // Launch a dummy process (somebody to send the AuthenticateMessage). + UPID pid = spawn(new ProcessBase(), true); + + Credential credential; + credential.set_principal("benh"); + credential.set_secret("secret"); + + Authenticatee authenticatee(credential, UPID()); + + Future<Message> message = + FUTURE_MESSAGE(Eq(AuthenticateMessage().GetTypeName()), _, _); + + Future<bool> client = authenticatee.authenticate(pid); + + AWAIT_READY(message); + + Authenticator authenticator(message.get().from); + + Future<Option<string> > server = authenticator.authenticate(); + + AWAIT_EQ(false, client); + AWAIT_READY(server); + EXPECT_NONE(server.get()); + + terminate(pid); +} + + +// This test verifies that the pending future returned by +// 'Authenticator::authenticate()' is properly failed when the Authenticator is +// destructed in the middle of authentication. +TEST(CRAMMD5Authentication, AuthenticatorDestructionRace) +{ + // Set up secrets. + map<string, string> secrets; + secrets["benh"] = "secret"; + cram_md5::secrets::load(secrets); + + // Launch a dummy process (somebody to send the AuthenticateMessage). + UPID pid = spawn(new ProcessBase(), true); + + Credential credential; + credential.set_principal("benh"); + credential.set_secret("secret"); + + Authenticatee authenticatee(credential, UPID()); + + Future<Message> message = + FUTURE_MESSAGE(Eq(AuthenticateMessage().GetTypeName()), _, _); + + Future<bool> client = authenticatee.authenticate(pid); + + AWAIT_READY(message); + + Authenticator* authenticator = new Authenticator(message.get().from); + + // Drop the AuthenticationStepMessage from authenticator to keep + // the authentication from getting completed. + Future<AuthenticationStepMessage> authenticationStepMessage = + DROP_PROTOBUF(AuthenticationStepMessage(), _, _); + + Future<Option<string> > principal = authenticator->authenticate(); + + AWAIT_READY(authenticationStepMessage); + + // At this point 'AuthenticatorProcess::authenticate()' has been + // executed and its promise associated with the promise returned + // by 'Authenticator::authenticate()'. + // Authentication should be pending. + ASSERT_TRUE(principal.isPending()); + + // Now delete the authenticator. + delete authenticator; + + // The future should be failed at this point. + AWAIT_FAILED(principal); + + terminate(pid); +} + +} // namespace cram_md5 { +} // namespace internal { +} // namespace mesos {
