You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by me...@apache.org on 2016/06/24 08:24:40 UTC
[1/3] mesos git commit: Updated slave API_HELP to report as
authenticated.
Repository: mesos
Updated Branches:
refs/heads/master 037c85b05 -> 9c9823bf5
Updated slave API_HELP to report as authenticated.
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/fe43822a
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/fe43822a
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/fe43822a
Branch: refs/heads/master
Commit: fe43822a0ad71b5b333c77ade6b3d51dc99575ef
Parents: 037c85b
Author: Adam B <ad...@mesosphere.io>
Authored: Fri Jun 24 00:20:43 2016 -0700
Committer: Adam B <ad...@mesosphere.io>
Committed: Fri Jun 24 00:20:43 2016 -0700
----------------------------------------------------------------------
src/slave/http.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mesos/blob/fe43822a/src/slave/http.cpp
----------------------------------------------------------------------
diff --git a/src/slave/http.cpp b/src/slave/http.cpp
index 22b5930..9475a0d 100644
--- a/src/slave/http.cpp
+++ b/src/slave/http.cpp
@@ -211,7 +211,7 @@ string Slave::Http::API_HELP()
"Endpoint for API calls against the agent."),
DESCRIPTION(
"Returns 200 OK if the call is successful"),
- AUTHENTICATION(false));
+ AUTHENTICATION(true));
}
[2/3] mesos git commit: Fixed failed authorization of '/files/*'
endpoints.
Posted by me...@apache.org.
Fixed failed authorization of '/files/*' endpoints.
The `FilesProcess` stores an authorization callback
for every virtual path. If no callback is found when
authorizing a request, then it is assumed that path
is not authorizable, so the request is approved.
This led to a bug in which the callbacks for paths
with trailing slashes were not correctly identified,
leading to requests being incorrectly authorized.
This patch trims trailing slashes before comparing
the requested path with the authorized paths.
Review: https://reviews.apache.org/r/49131/
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/6852825d
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/6852825d
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/6852825d
Branch: refs/heads/master
Commit: 6852825da519024de4713b3571e65f1fd5af60ef
Parents: fe43822
Author: Greg Mann <gr...@mesosphere.io>
Authored: Fri Jun 24 00:21:16 2016 -0700
Committer: Adam B <ad...@mesosphere.io>
Committed: Fri Jun 24 00:21:16 2016 -0700
----------------------------------------------------------------------
src/files/files.cpp | 9 +++++++--
src/tests/files_tests.cpp | 40 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 47 insertions(+), 2 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mesos/blob/6852825d/src/files/files.cpp
----------------------------------------------------------------------
diff --git a/src/files/files.cpp b/src/files/files.cpp
index 20bc6fa..a5a1b86 100644
--- a/src/files/files.cpp
+++ b/src/files/files.cpp
@@ -315,8 +315,13 @@ Future<bool> FilesProcess::authorize(
string requestedPath,
const Option<string>& principal)
{
- if (authorizations.count(requestedPath) > 0) {
- return authorizations[requestedPath](principal);
+ // The path may contain a trailing forward slash. Since we store the
+ // authorization callbacks without the trailing slash, we must remove it here,
+ // if present.
+ string trimmedPath = strings::remove(requestedPath, "/", strings::SUFFIX);
+
+ if (authorizations.count(trimmedPath) > 0) {
+ return authorizations[trimmedPath](principal);
}
do {
http://git-wip-us.apache.org/repos/asf/mesos/blob/6852825d/src/tests/files_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/files_tests.cpp b/src/tests/files_tests.cpp
index b8f4f3c..31337e2 100644
--- a/src/tests/files_tests.cpp
+++ b/src/tests/files_tests.cpp
@@ -303,6 +303,9 @@ TEST_F(FilesTest, ResolveTest)
AWAIT_EXPECT_RESPONSE_STATUS_EQ(
BadRequest().status,
process::http::get(upid, "read", "path=one/"));
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(
+ BadRequest().status,
+ process::http::get(upid, "read", "path=one/two/"));
// Breaking out of sandbox.
AWAIT_EXPECT_RESPONSE_STATUS_EQ(
@@ -320,6 +323,7 @@ TEST_F(FilesTest, BrowseTest)
ASSERT_SOME(os::mkdir("1/3"));
ASSERT_SOME(os::write("1/two", "two"));
ASSERT_SOME(os::write("1/three", "three"));
+ ASSERT_SOME(os::mkdir("2"));
AWAIT_EXPECT_READY(files.attach("1", "one"));
@@ -370,18 +374,54 @@ TEST_F(FilesTest, BrowseTest)
return authorized;
};
+ ASSERT_SOME(os::mkdir("2"));
+
AWAIT_EXPECT_READY(files.attach("1", "one", authorization));
+ AWAIT_EXPECT_READY(files.attach("2", "/two/", authorization));
+
+ // The `FilesProcess` stores authorization callbacks in a map keyed by path.
+ // If no callback is found for the requested path, then it is assumed that
+ // authorization for that path is not enabled - the request is authorized.
+ // Because of this, it is worth testing several permutations of the process's
+ // handling of trailing slashes in path names when authorization is enabled.
+ // We sometimes remove trailing slashes, so it's possible that we could fail
+ // to find the callback in the map.
response = process::http::get(upid, "browse", "path=one");
AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response);
AWAIT_EXPECT_RESPONSE_BODY_EQ(stringify(expected), response);
+ response = process::http::get(upid, "browse", "path=one/");
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response);
+ AWAIT_EXPECT_RESPONSE_BODY_EQ(stringify(expected), response);
+
+ response = process::http::get(upid, "browse", "path=/two");
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response);
+
+ response = process::http::get(upid, "browse", "path=/two/");
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response);
+
authorized = false;
response = process::http::get(upid, "browse", "path=one");
AWAIT_EXPECT_RESPONSE_STATUS_EQ(Forbidden().status, response);
+
+ response = process::http::get(upid, "browse", "path=one/");
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(Forbidden().status, response);
+
+ response = process::http::get(upid, "browse", "path=/two");
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(Forbidden().status, response);
+
+ response = process::http::get(upid, "browse", "path=/two/");
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(Forbidden().status, response);
}
[3/3] mesos git commit: Enabled fine grained authorization in the
Agent.
Posted by me...@apache.org.
Enabled fine grained authorization in the Agent.
While information about frameworks, executors and tasks were well
protected in the master, this information was not protected in the
agents, which enabled unauthorized users to verify the data by getting
the agent `/state` endpoint. This was particularly pressing since the
Mesos UI would work as a proxy for agents endpoints so even being
behind a firewall would not had been enough.
Review: https://reviews.apache.org/r/49082/
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/9c9823bf
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/9c9823bf
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/9c9823bf
Branch: refs/heads/master
Commit: 9c9823bf5b6940e4f05fdae4fa0b339af7a57171
Parents: 6852825
Author: Alexander Rojas <al...@mesosphere.io>
Authored: Fri Jun 24 00:25:43 2016 -0700
Committer: Adam B <ad...@mesosphere.io>
Committed: Fri Jun 24 00:27:34 2016 -0700
----------------------------------------------------------------------
src/common/http.cpp | 77 ++++++
src/common/http.hpp | 36 +++
src/master/http.cpp | 88 -------
src/slave/http.cpp | 348 +++++++++++++++++++--------
src/tests/mesos.cpp | 19 ++
src/tests/mesos.hpp | 8 +
src/tests/slave_authorization_tests.cpp | 194 +++++++++++++++
7 files changed, 576 insertions(+), 194 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mesos/blob/9c9823bf/src/common/http.cpp
----------------------------------------------------------------------
diff --git a/src/common/http.cpp b/src/common/http.cpp
index 4b35736..5cc9e86 100644
--- a/src/common/http.cpp
+++ b/src/common/http.cpp
@@ -47,6 +47,8 @@ using std::set;
using std::string;
using std::vector;
+using process::Owned;
+
using process::http::authorization::AuthorizationCallbacks;
namespace mesos {
@@ -620,4 +622,79 @@ const AuthorizationCallbacks createAuthorizationCallbacks(
return callbacks;
}
+bool approveViewFrameworkInfo(
+ const Owned<ObjectApprover>& frameworksApprover,
+ const FrameworkInfo& frameworkInfo)
+{
+ ObjectApprover::Object object;
+ object.framework_info = &frameworkInfo;
+
+ Try<bool> approved = frameworksApprover->approved(object);
+ if (approved.isError()) {
+ LOG(WARNING) << "Error during FrameworkInfo authorization: "
+ << approved.error();
+ // TODO(joerg84): Consider exposing these errors to the caller.
+ return false;
+ }
+ return approved.get();
+}
+
+
+bool approveViewExecutorInfo(
+ const Owned<ObjectApprover>& executorsApprover,
+ const ExecutorInfo& executorInfo,
+ const FrameworkInfo& frameworkInfo)
+{
+ ObjectApprover::Object object;
+ object.executor_info = &executorInfo;
+ object.framework_info = &frameworkInfo;
+
+ Try<bool> approved = executorsApprover->approved(object);
+ if (approved.isError()) {
+ LOG(WARNING) << "Error during ExecutorInfo authorization: "
+ << approved.error();
+ // TODO(joerg84): Consider exposing these errors to the caller.
+ return false;
+ }
+ return approved.get();
+}
+
+
+bool approveViewTaskInfo(
+ const Owned<ObjectApprover>& tasksApprover,
+ const TaskInfo& taskInfo,
+ const FrameworkInfo& frameworkInfo)
+{
+ ObjectApprover::Object object;
+ object.task_info = &taskInfo;
+ object.framework_info = &frameworkInfo;
+
+ Try<bool> approved = tasksApprover->approved(object);
+ if (approved.isError()) {
+ LOG(WARNING) << "Error during TaskInfo authorization: " << approved.error();
+ // TODO(joerg84): Consider exposing these errors to the caller.
+ return false;
+ }
+ return approved.get();
+}
+
+
+bool approveViewTask(
+ const Owned<ObjectApprover>& tasksApprover,
+ const Task& task,
+ const FrameworkInfo& frameworkInfo)
+{
+ ObjectApprover::Object object;
+ object.task = &task;
+ object.framework_info = &frameworkInfo;
+
+ Try<bool> approved = tasksApprover->approved(object);
+ if (approved.isError()) {
+ LOG(WARNING) << "Error during Task authorization: " << approved.error();
+ // TODO(joerg84): Consider exposing these errors to the caller.
+ return false;
+ }
+ return approved.get();
+}
+
} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/9c9823bf/src/common/http.hpp
----------------------------------------------------------------------
diff --git a/src/common/http.hpp b/src/common/http.hpp
index 588a1eb..55bd0ac 100644
--- a/src/common/http.hpp
+++ b/src/common/http.hpp
@@ -26,6 +26,7 @@
#include <process/future.hpp>
#include <process/http.hpp>
+#include <process/owned.hpp>
#include <stout/hashmap.hpp>
#include <stout/json.hpp>
@@ -100,6 +101,41 @@ void json(JSON::ObjectWriter* writer, const TaskStatus& status);
const process::http::authorization::AuthorizationCallbacks
createAuthorizationCallbacks(Authorizer* authorizer);
+
+// Implementation of the `ObjectApprover` interface authorizing all objects.
+class AcceptingObjectApprover : public ObjectApprover
+{
+public:
+ virtual Try<bool> approved(
+ const Option<ObjectApprover::Object>& object) const noexcept override
+ {
+ return true;
+ }
+};
+
+
+bool approveViewFrameworkInfo(
+ const process::Owned<ObjectApprover>& frameworksApprover,
+ const FrameworkInfo& frameworkInfo);
+
+
+bool approveViewExecutorInfo(
+ const process::Owned<ObjectApprover>& executorsApprover,
+ const ExecutorInfo& executorInfo,
+ const FrameworkInfo& frameworkInfo);
+
+
+bool approveViewTaskInfo(
+ const process::Owned<ObjectApprover>& tasksApprover,
+ const TaskInfo& taskInfo,
+ const FrameworkInfo& frameworkInfo);
+
+
+bool approveViewTask(
+ const process::Owned<ObjectApprover>& tasksApprover,
+ const Task& task,
+ const FrameworkInfo& frameworkInfo);
+
} // namespace mesos {
#endif // __COMMON_HTTP_HPP__
http://git-wip-us.apache.org/repos/asf/mesos/blob/9c9823bf/src/master/http.cpp
----------------------------------------------------------------------
diff --git a/src/master/http.cpp b/src/master/http.cpp
index 70f084b..837fba6 100644
--- a/src/master/http.cpp
+++ b/src/master/http.cpp
@@ -169,94 +169,6 @@ struct Full : Representation<T>
};
-// Implementation of the `ObjectApprover` interface authorizing all objects.
-class AcceptingObjectApprover : public ObjectApprover
-{
-public:
- virtual Try<bool> approved(
- const Option<ObjectApprover::Object>& object) const noexcept override
- {
- return true;
- }
-};
-
-
-static bool approveViewFrameworkInfo(
- const Owned<ObjectApprover>& frameworksApprover,
- const FrameworkInfo& frameworkInfo)
-{
- ObjectApprover::Object object;
- object.framework_info = &frameworkInfo;
-
- Try<bool> approved = frameworksApprover->approved(object);
- if (approved.isError()) {
- LOG(WARNING) << "Error during FrameworkInfo authorization: "
- << approved.error();
- // TODO(joerg84): Consider exposing these errors to the caller.
- return false;
- }
- return approved.get();
-}
-
-
-static bool approveViewTaskInfo(
- const Owned<ObjectApprover>& tasksApprover,
- const TaskInfo& taskInfo,
- const FrameworkInfo& frameworkInfo)
-{
- ObjectApprover::Object object;
- object.task_info = &taskInfo;
- object.framework_info = &frameworkInfo;
-
- Try<bool> approved = tasksApprover->approved(object);
- if (approved.isError()) {
- LOG(WARNING) << "Error during TaskInfo authorization: " << approved.error();
- // TODO(joerg84): Consider exposing these errors to the caller.
- return false;
- }
- return approved.get();
-}
-
-
-static bool approveViewTask(
- const Owned<ObjectApprover>& tasksApprover,
- const Task& task,
- const FrameworkInfo& frameworkInfo)
-{
- ObjectApprover::Object object;
- object.task = &task;
- object.framework_info = &frameworkInfo;
-
- Try<bool> approved = tasksApprover->approved(object);
- if (approved.isError()) {
- LOG(WARNING) << "Error during Task authorization: " << approved.error();
- // TODO(joerg84): Consider exposing these errors to the caller.
- return false;
- }
- return approved.get();
-}
-
-
-static bool approveViewExecutorInfo(
- const Owned<ObjectApprover>& executorsApprover,
- const ExecutorInfo& executorInfo,
- const FrameworkInfo& frameworkInfo)
-{
- ObjectApprover::Object object;
- object.executor_info = &executorInfo;
- object.framework_info = &frameworkInfo;
-
- Try<bool> approved = executorsApprover->approved(object);
- if (approved.isError()) {
- LOG(WARNING) << "Error during ExecutorInfo authorization: "
- << approved.error();
- // TODO(joerg84): Consider exposing these errors to the caller.
- return false;
- }
- return approved.get();
-}
-
-
// Forward declaration for `FullFrameworkWriter`.
static void json(JSON::ObjectWriter* writer, const Summary<Framework>& summary);
http://git-wip-us.apache.org/repos/asf/mesos/blob/9c9823bf/src/slave/http.cpp
----------------------------------------------------------------------
diff --git a/src/slave/http.cpp b/src/slave/http.cpp
index 9475a0d..c038bf0 100644
--- a/src/slave/http.cpp
+++ b/src/slave/http.cpp
@@ -91,6 +91,7 @@ using process::metrics::internal::MetricsProcess;
using std::list;
using std::string;
+using std::tie;
using std::tuple;
using std::vector;
@@ -123,69 +124,141 @@ using process::http::Response;
using process::http::Request;
-static void json(JSON::ObjectWriter* writer, const Executor& executor)
+// Filtered representation of an Executor. Tasks within this executor
+// are filtered based on whether the user is authorized to view them.
+struct ExecutorWriter
{
- writer->field("id", executor.id.value());
- writer->field("name", executor.info.name());
- writer->field("source", executor.info.source());
- writer->field("container", executor.containerId.value());
- writer->field("directory", executor.directory);
- writer->field("resources", executor.resources);
-
- if (executor.info.has_labels()) {
- writer->field("labels", executor.info.labels());
- }
-
- writer->field("tasks", [&executor](JSON::ArrayWriter* writer) {
- foreach (Task* task, executor.launchedTasks.values()) {
- writer->element(*task);
+ ExecutorWriter(
+ const Owned<ObjectApprover>& taskApprover,
+ const Executor* executor,
+ const Framework* framework)
+ : taskApprover_(taskApprover),
+ executor_(executor),
+ framework_(framework) {}
+
+ void operator()(JSON::ObjectWriter* writer) const
+ {
+ writer->field("id", executor_->id.value());
+ writer->field("name", executor_->info.name());
+ writer->field("source", executor_->info.source());
+ writer->field("container", executor_->containerId.value());
+ writer->field("directory", executor_->directory);
+ writer->field("resources", executor_->resources);
+
+ if (executor_->info.has_labels()) {
+ writer->field("labels", executor_->info.labels());
}
- });
- writer->field("queued_tasks", [&executor](JSON::ArrayWriter* writer) {
- foreach (const TaskInfo& task, executor.queuedTasks.values()) {
- writer->element(task);
- }
- });
+ writer->field("tasks", [this](JSON::ArrayWriter* writer) {
+ foreach (Task* task, executor_->launchedTasks.values()) {
+ if (!approveViewTask(taskApprover_, *task, framework_->info)) {
+ continue;
+ }
- writer->field("completed_tasks", [&executor](JSON::ArrayWriter* writer) {
- foreach (const std::shared_ptr<Task>& task, executor.completedTasks) {
- writer->element(*task);
- }
+ writer->element(*task);
+ }
+ });
- // NOTE: We add 'terminatedTasks' to 'completed_tasks' for
- // simplicity.
- // TODO(vinod): Use foreachvalue instead once LinkedHashmap
- // supports it.
- foreach (Task* task, executor.terminatedTasks.values()) {
- writer->element(*task);
- }
- });
-}
+ writer->field("queued_tasks", [this](JSON::ArrayWriter* writer) {
+ foreach (const TaskInfo& task, executor_->queuedTasks.values()) {
+ if (!approveViewTaskInfo(taskApprover_, task, framework_->info)) {
+ continue;
+ }
+
+ writer->element(task);
+ }
+ });
+
+ writer->field("completed_tasks", [this](JSON::ArrayWriter* writer) {
+ foreach (const std::shared_ptr<Task>& task, executor_->completedTasks) {
+ if (!approveViewTask(taskApprover_, *task, framework_->info)) {
+ continue;
+ }
+ writer->element(*task);
+ }
+
+ // NOTE: We add 'terminatedTasks' to 'completed_tasks' for
+ // simplicity.
+ // TODO(vinod): Use foreachvalue instead once LinkedHashmap
+ // supports it.
+ foreach (Task* task, executor_->terminatedTasks.values()) {
+ if (!approveViewTask(taskApprover_, *task, framework_->info)) {
+ continue;
+ }
+
+ writer->element(*task);
+ }
+ });
+ }
+
+ const Owned<ObjectApprover>& taskApprover_;
+ const Executor* executor_;
+ const Framework* framework_;
+};
-static void json(JSON::ObjectWriter* writer, const Framework& framework)
+// Filtered representation of FrameworkInfo.
+// Executors and Tasks are filtered based on whether the
+// user is authorized to view them.
+struct FrameworkWriter
{
- writer->field("id", framework.id().value());
- writer->field("name", framework.info.name());
- writer->field("user", framework.info.user());
- writer->field("failover_timeout", framework.info.failover_timeout());
- writer->field("checkpoint", framework.info.checkpoint());
- writer->field("role", framework.info.role());
- writer->field("hostname", framework.info.hostname());
-
- writer->field("executors", [&framework](JSON::ArrayWriter* writer) {
- foreachvalue (Executor* executor, framework.executors) {
- writer->element(*executor);
- }
- });
+ FrameworkWriter(
+ const Owned<ObjectApprover>& taskApprover,
+ const Owned<ObjectApprover>& executorApprover,
+ const Framework* framework)
+ : taskApprover_(taskApprover),
+ executorApprover_(executorApprover),
+ framework_(framework) {}
+
+ void operator()(JSON::ObjectWriter* writer) const
+ {
+ writer->field("id", framework_->id().value());
+ writer->field("name", framework_->info.name());
+ writer->field("user", framework_->info.user());
+ writer->field("failover_timeout", framework_->info.failover_timeout());
+ writer->field("checkpoint", framework_->info.checkpoint());
+ writer->field("role", framework_->info.role());
+ writer->field("hostname", framework_->info.hostname());
+
+ writer->field("executors", [this](JSON::ArrayWriter* writer) {
+ foreachvalue (Executor* executor, framework_->executors) {
+ if (!approveViewExecutorInfo(
+ executorApprover_, executor->info, framework_->info)) {
+ continue;
+ }
- writer->field("completed_executors", [&framework](JSON::ArrayWriter* writer) {
- foreach (const Owned<Executor>& executor, framework.completedExecutors) {
- writer->element(*executor);
- }
- });
-}
+ ExecutorWriter executorWriter(
+ taskApprover_,
+ executor,
+ framework_);
+
+ writer->element(executorWriter);
+ }
+ });
+
+ writer->field(
+ "completed_executors", [this](JSON::ArrayWriter* writer) {
+ foreach (
+ const Owned<Executor>& executor, framework_->completedExecutors) {
+ if (!approveViewExecutorInfo(
+ executorApprover_, executor->info, framework_->info)) {
+ continue;
+ }
+
+ ExecutorWriter executorWriter(
+ taskApprover_,
+ executor.get(),
+ framework_);
+
+ writer->element(executorWriter);
+ }
+ });
+ }
+
+ const Owned<ObjectApprover>& taskApprover_;
+ const Owned<ObjectApprover>& executorApprover_;
+ const Framework* framework_;
+};
void Slave::Http::log(const Request& request)
@@ -752,78 +825,141 @@ string Slave::Http::STATE_HELP() {
Future<Response> Slave::Http::state(
const Request& request,
- const Option<string>& /* principal */) const
+ const Option<string>& principal) const
{
if (slave->state == Slave::RECOVERING) {
return ServiceUnavailable("Agent has not finished recovery");
}
- auto state = [this](JSON::ObjectWriter* writer) {
- writer->field("version", MESOS_VERSION);
+ // Retrieve `ObjectApprover`s for authorizing frameworks and tasks.
+ Future<Owned<ObjectApprover>> frameworksApprover;
+ Future<Owned<ObjectApprover>> tasksApprover;
+ Future<Owned<ObjectApprover>> executorsApprover;
- if (build::GIT_SHA.isSome()) {
- writer->field("git_sha", build::GIT_SHA.get());
+ if (slave->authorizer.isSome()) {
+ authorization::Subject subject;
+ if (principal.isSome()) {
+ subject.set_value(principal.get());
}
- if (build::GIT_BRANCH.isSome()) {
- writer->field("git_branch", build::GIT_BRANCH.get());
- }
+ frameworksApprover = slave->authorizer.get()->getObjectApprover(
+ subject, authorization::VIEW_FRAMEWORK);
- if (build::GIT_TAG.isSome()) {
- writer->field("git_tag", build::GIT_TAG.get());
- }
+ tasksApprover = slave->authorizer.get()->getObjectApprover(
+ subject, authorization::VIEW_TASK);
- writer->field("build_date", build::DATE);
- writer->field("build_time", build::TIME);
- writer->field("build_user", build::USER);
- writer->field("start_time", slave->startTime.secs());
+ executorsApprover = slave->authorizer.get()->getObjectApprover(
+ subject, authorization::VIEW_EXECUTOR);
+ } else {
+ frameworksApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
+ tasksApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
+ executorsApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
+ }
- writer->field("id", slave->info.id().value());
- writer->field("pid", string(slave->self()));
- writer->field("hostname", slave->info.hostname());
+ return collect(frameworksApprover, tasksApprover, executorsApprover)
+ .then(defer(slave->self(),
+ [this, request](const tuple<Owned<ObjectApprover>,
+ Owned<ObjectApprover>,
+ Owned<ObjectApprover>>& approvers) -> Response {
+ auto state = [this, approvers](JSON::ObjectWriter* writer) {
+ // Get approver from tuple.
+ Owned<ObjectApprover> frameworksApprover;
+ Owned<ObjectApprover> tasksApprover;
+ Owned<ObjectApprover> executorsApprover;
+ tie(frameworksApprover, tasksApprover, executorsApprover) = approvers;
+
+ writer->field("version", MESOS_VERSION);
+
+ if (build::GIT_SHA.isSome()) {
+ writer->field("git_sha", build::GIT_SHA.get());
+ }
- writer->field("resources", Resources(slave->info.resources()));
- writer->field("attributes", Attributes(slave->info.attributes()));
+ if (build::GIT_BRANCH.isSome()) {
+ writer->field("git_branch", build::GIT_BRANCH.get());
+ }
- if (slave->master.isSome()) {
- Try<string> hostname = net::getHostname(slave->master.get().address.ip);
- if (hostname.isSome()) {
- writer->field("master_hostname", hostname.get());
- }
- }
+ if (build::GIT_TAG.isSome()) {
+ writer->field("git_tag", build::GIT_TAG.get());
+ }
- if (slave->flags.log_dir.isSome()) {
- writer->field("log_dir", slave->flags.log_dir.get());
- }
+ writer->field("build_date", build::DATE);
+ writer->field("build_time", build::TIME);
+ writer->field("build_user", build::USER);
+ writer->field("start_time", slave->startTime.secs());
- if (slave->flags.external_log_file.isSome()) {
- writer->field("external_log_file", slave->flags.external_log_file.get());
- }
+ writer->field("id", slave->info.id().value());
+ writer->field("pid", string(slave->self()));
+ writer->field("hostname", slave->info.hostname());
- writer->field("frameworks", [this](JSON::ArrayWriter* writer) {
- foreachvalue (Framework* framework, slave->frameworks) {
- writer->element(*framework);
- }
- });
+ writer->field("resources", Resources(slave->info.resources()));
+ writer->field("attributes", Attributes(slave->info.attributes()));
- // Model all of the completed frameworks.
- writer->field("completed_frameworks", [this](JSON::ArrayWriter* writer) {
- foreach (const Owned<Framework>& framework, slave->completedFrameworks) {
- writer->element(*framework);
- }
- });
+ if (slave->master.isSome()) {
+ Try<string> hostname =
+ net::getHostname(slave->master.get().address.ip);
- writer->field("flags", [this](JSON::ObjectWriter* writer) {
- foreachvalue (const flags::Flag& flag, slave->flags) {
- Option<string> value = flag.stringify(slave->flags);
- if (value.isSome()) {
- writer->field(flag.effective_name().value, value.get());
+ if (hostname.isSome()) {
+ writer->field("master_hostname", hostname.get());
+ }
}
- }
- });
- };
- return OK(jsonify(state), request.url.query.get("jsonp"));
+ if (slave->flags.log_dir.isSome()) {
+ writer->field("log_dir", slave->flags.log_dir.get());
+ }
+
+ if (slave->flags.external_log_file.isSome()) {
+ writer->field(
+ "external_log_file", slave->flags.external_log_file.get());
+ }
+
+ writer->field("frameworks", [=](JSON::ArrayWriter* writer) {
+ foreachvalue (Framework* framework, slave->frameworks) {
+ // Skip unauthorized frameworks.
+ if (!approveViewFrameworkInfo(
+ frameworksApprover, framework->info)) {
+ continue;
+ }
+
+ FrameworkWriter frameworkWriter(
+ tasksApprover,
+ executorsApprover,
+ framework);
+
+ writer->element(frameworkWriter);
+ }
+ });
+
+ // Model all of the completed frameworks.
+ writer->field("completed_frameworks", [=](JSON::ArrayWriter* writer) {
+ foreach (const Owned<Framework>& framework,
+ slave->completedFrameworks) {
+ // Skip unauthorized frameworks.
+ if (!approveViewFrameworkInfo(
+ frameworksApprover, framework->info)) {
+ continue;
+ }
+
+ FrameworkWriter frameworkWriter(
+ tasksApprover,
+ executorsApprover,
+ framework.get());
+
+ writer->element(frameworkWriter);
+ }
+ });
+
+ writer->field("flags", [this](JSON::ObjectWriter* writer) {
+ foreachvalue (const flags::Flag& flag, slave->flags) {
+ Option<string> value = flag.stringify(slave->flags);
+ if (value.isSome()) {
+ writer->field(flag.effective_name().value, value.get());
+ }
+ }
+ });
+ };
+
+ return OK(jsonify(state), request.url.query.get("jsonp"));
+ }));
}
http://git-wip-us.apache.org/repos/asf/mesos/blob/9c9823bf/src/tests/mesos.cpp
----------------------------------------------------------------------
diff --git a/src/tests/mesos.cpp b/src/tests/mesos.cpp
index 15aaaa7..6918343 100644
--- a/src/tests/mesos.cpp
+++ b/src/tests/mesos.cpp
@@ -424,6 +424,25 @@ Try<Owned<cluster::Slave>> MesosTest::StartSlave(
}
+Try<Owned<cluster::Slave>> MesosTest::StartSlave(
+ mesos::master::detector::MasterDetector* detector,
+ slave::Containerizer* containerizer,
+ mesos::Authorizer* authorizer,
+ const Option<slave::Flags>& flags)
+{
+ return cluster::Slave::start(
+ detector,
+ flags.isNone() ? CreateSlaveFlags() : flags.get(),
+ None(),
+ containerizer,
+ None(),
+ None(),
+ None(),
+ None(),
+ authorizer);
+}
+
+
// Although the constructors and destructors for mock classes are
// often trivial, defining them out-of-line (in a separate compilation
// unit) improves compilation time: see MESOS-3827.
http://git-wip-us.apache.org/repos/asf/mesos/blob/9c9823bf/src/tests/mesos.hpp
----------------------------------------------------------------------
diff --git a/src/tests/mesos.hpp b/src/tests/mesos.hpp
index b9de9b6..bcd060e 100644
--- a/src/tests/mesos.hpp
+++ b/src/tests/mesos.hpp
@@ -219,6 +219,14 @@ protected:
mesos::Authorizer* authorizer,
const Option<slave::Flags>& flags = None());
+ // Starts a slave with the specified detector, containerizer, authorizer,
+ // and flags.
+ virtual Try<process::Owned<cluster::Slave>> StartSlave(
+ mesos::master::detector::MasterDetector* detector,
+ slave::Containerizer* containerizer,
+ mesos::Authorizer* authorizer,
+ const Option<slave::Flags>& flags = None());
+
Option<zookeeper::URL> zookeeperUrl;
const std::string defaultAgentResourcesString{
http://git-wip-us.apache.org/repos/asf/mesos/blob/9c9823bf/src/tests/slave_authorization_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/slave_authorization_tests.cpp b/src/tests/slave_authorization_tests.cpp
index 98fde49..18bcb0e 100644
--- a/src/tests/slave_authorization_tests.cpp
+++ b/src/tests/slave_authorization_tests.cpp
@@ -33,6 +33,7 @@
#include "master/detector/standalone.hpp"
+#include "tests/containerizer.hpp"
#include "tests/mesos.hpp"
#include "tests/module.hpp"
@@ -42,6 +43,7 @@ using mesos::internal::master::Master;
using mesos::internal::slave::Slave;
+using mesos::master::detector::MasterDetector;
using mesos::master::detector::StandaloneMasterDetector;
using process::Future;
@@ -53,6 +55,7 @@ using process::http::Response;
using std::string;
+using testing::AtMost;
using testing::DoAll;
namespace mesos {
@@ -162,6 +165,197 @@ TYPED_TEST(SlaveAuthorizerTest, AuthorizeFlagsEndpointWithoutPrincipal)
}
+// This test verifies that authorization based endpoint filtering
+// works correctly on the /state endpoint.
+// Both default users are allowed to to view high level frameworks, but only
+// one is allowed to view the tasks.
+TYPED_TEST(SlaveAuthorizerTest, FilterStateEndpoint)
+{
+ ACLs acls;
+
+ {
+ // Default principal can see all frameworks.
+ mesos::ACL::ViewFramework* acl = acls.add_view_frameworks();
+ acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL.principal());
+ acl->mutable_users()->set_type(ACL::Entity::ANY);
+ }
+
+ {
+ // Second default principal can see all frameworks.
+ mesos::ACL::ViewFramework* acl = acls.add_view_frameworks();
+ acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL_2.principal());
+ acl->mutable_users()->set_type(ACL::Entity::ANY);
+ }
+
+ {
+ // No other principal can see frameworks running under any user.
+ ACL::ViewFramework* acl = acls.add_view_frameworks();
+ acl->mutable_principals()->set_type(ACL::Entity::ANY);
+ acl->mutable_users()->set_type(ACL::Entity::NONE);
+ }
+
+ {
+ // Default principal can see all executors.
+ mesos::ACL::ViewExecutor* acl = acls.add_view_executors();
+ acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL.principal());
+ acl->mutable_users()->set_type(ACL::Entity::ANY);
+ }
+
+ {
+ // No other principal can see executors running under any user.
+ ACL::ViewExecutor* acl = acls.add_view_executors();
+ acl->mutable_principals()->set_type(ACL::Entity::ANY);
+ acl->mutable_users()->set_type(ACL::Entity::NONE);
+ }
+
+ {
+ // Default principal can see all tasks.
+ mesos::ACL::ViewTask* acl = acls.add_view_tasks();
+ acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL.principal());
+ acl->mutable_users()->set_type(ACL::Entity::ANY);
+ }
+
+ {
+ // No other principal can see tasks running under any user.
+ ACL::ViewTask* acl = acls.add_view_tasks();
+ acl->mutable_principals()->set_type(ACL::Entity::ANY);
+ acl->mutable_users()->set_type(ACL::Entity::NONE);
+ }
+
+ // Create an `Authorizer` with the ACLs.
+ Try<Authorizer*> create = TypeParam::create(parameterize(acls));
+ ASSERT_SOME(create);
+ Owned<Authorizer> authorizer(create.get());
+
+ Try<Owned<cluster::Master>> master = this->StartMaster(authorizer.get());
+ ASSERT_SOME(master);
+
+ // Resgister framework with user "bar".
+ FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO;
+ frameworkInfo.set_role("role");
+ frameworkInfo.set_user("bar");
+
+ // Create an executor with user "bar".
+ ExecutorInfo executor = CREATE_EXECUTOR_INFO("test-executor", "sleep 2");
+ executor.mutable_command()->set_user("bar");
+
+ MockExecutor exec(executor.executor_id());
+ TestContainerizer containerizer(&exec);
+
+ Owned<MasterDetector> detector = master.get()->createDetector();
+ Try<Owned<cluster::Slave>> slave =
+ this->StartSlave(detector.get(), &containerizer, authorizer.get());
+
+ ASSERT_SOME(slave);
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, frameworkInfo, master.get()->pid, DEFAULT_CREDENTIAL);
+
+ EXPECT_CALL(exec, registered(_, _, _, _))
+ .Times(AtMost(1));
+
+ Future<Nothing> registered;
+ EXPECT_CALL(sched, registered(&driver, _, _))
+ .WillOnce(FutureSatisfy(®istered))
+ .WillRepeatedly(Return());
+
+ Future<vector<Offer>> offers;
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillOnce(FutureArg<1>(&offers))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(registered);
+
+ AWAIT_READY(offers);
+ EXPECT_NE(0u, offers.get().size());
+
+ TaskInfo task;
+ task.set_name("test");
+ task.mutable_task_id()->set_value("1");
+ task.mutable_slave_id()->MergeFrom(offers.get()[0].slave_id());
+ task.mutable_resources()->MergeFrom(offers.get()[0].resources());
+ task.mutable_executor()->MergeFrom(executor);
+
+ EXPECT_CALL(exec, launchTask(_, _))
+ .WillOnce(SendStatusUpdateFromTask(TASK_RUNNING))
+ .WillRepeatedly(Return());
+
+ Future<TaskStatus> status;
+ EXPECT_CALL(sched, statusUpdate(&driver, _))
+ .WillOnce(FutureArg<1>(&status));
+
+ driver.launchTasks(offers.get()[0].id(), {task});
+
+ AWAIT_READY(status);
+ EXPECT_EQ(TASK_RUNNING, status.get().state());
+
+ // Retrieve endpoint with the user allowed to view the framework.
+ {
+ Future<Response> response = http::get(
+ slave.get()->pid,
+ "state",
+ None(),
+ createBasicAuthHeaders(DEFAULT_CREDENTIAL));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response)
+ << response.get().body;
+
+ Try<JSON::Object> parse = JSON::parse<JSON::Object>(response.get().body);
+ ASSERT_SOME(parse);
+
+ JSON::Object state = parse.get();
+
+ ASSERT_TRUE(state.values["frameworks"].is<JSON::Array>());
+
+ JSON::Array frameworks = state.values["frameworks"].as<JSON::Array>();
+ EXPECT_EQ(1u, frameworks.values.size());
+
+ JSON::Object framework = frameworks.values.front().as<JSON::Object>();
+ ASSERT_TRUE(framework.values["executors"].is<JSON::Array>());
+
+ JSON::Array executors = framework.values["executors"].as<JSON::Array>();
+ EXPECT_EQ(1u, executors.values.size());
+
+ JSON::Object executor = executors.values.front().as<JSON::Object>();
+ EXPECT_EQ(1u, executor.values["tasks"].as<JSON::Array>().values.size());
+ }
+
+ // Retrieve endpoint with the user allowed to view the framework,
+ // but not the executor.
+ {
+ Future<Response> response = http::get(
+ slave.get()->pid,
+ "state",
+ None(),
+ createBasicAuthHeaders(DEFAULT_CREDENTIAL_2));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response)
+ << response.get().body;
+
+ Try<JSON::Object> parse = JSON::parse<JSON::Object>(response.get().body);
+ ASSERT_SOME(parse);
+
+ JSON::Object state = parse.get();
+ ASSERT_TRUE(state.values["frameworks"].is<JSON::Array>());
+
+ JSON::Array frameworks = state.values["frameworks"].as<JSON::Array>();
+ EXPECT_EQ(1u, frameworks.values.size());
+
+ JSON::Object framework = frameworks.values.front().as<JSON::Object>();
+ EXPECT_TRUE(framework.values["executors"].as<JSON::Array>().values.empty());
+ }
+
+ EXPECT_CALL(exec, shutdown(_))
+ .Times(AtMost(1));
+
+ driver.stop();
+ driver.join();
+}
+
+
// Parameterized fixture for agent-specific authorization tests. The
// path of the tested endpoint is passed as the only parameter.
class SlaveEndpointTest: