You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by vi...@apache.org on 2017/04/21 17:45:55 UTC

[1/8] mesos git commit: Changed 'Principal.claims' to a hashmap.

Repository: mesos
Updated Branches:
  refs/heads/master ab6afe86d -> 3f5b2c90c


Changed 'Principal.claims' to a hashmap.

This patch changes the `claims` member of the authentication
`Principal` struct from a `std::map` to a `hashmap`, so that
we can make use of the `contains()` helper during authorization.

Review: https://reviews.apache.org/r/58251/


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/6d063880
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/6d063880
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/6d063880

Branch: refs/heads/master
Commit: 6d06388039f060dd091f543c0f83a2a205b39ae7
Parents: ab6afe8
Author: Greg Mann <gr...@mesosphere.io>
Authored: Fri Apr 21 10:45:09 2017 -0700
Committer: Vinod Kone <vi...@gmail.com>
Committed: Fri Apr 21 10:45:09 2017 -0700

----------------------------------------------------------------------
 3rdparty/libprocess/include/process/authenticator.hpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/6d063880/3rdparty/libprocess/include/process/authenticator.hpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/include/process/authenticator.hpp b/3rdparty/libprocess/include/process/authenticator.hpp
index 272d921..2a59081 100644
--- a/3rdparty/libprocess/include/process/authenticator.hpp
+++ b/3rdparty/libprocess/include/process/authenticator.hpp
@@ -47,7 +47,7 @@ struct Principal
 
   Principal(
       const Option<std::string>& _value,
-      const std::map<std::string, std::string>& _claims)
+      const hashmap<std::string, std::string>& _claims)
     : value(_value), claims(_claims) {}
 
   bool operator==(const Principal& that) const
@@ -66,7 +66,7 @@ struct Principal
   }
 
   Option<std::string> value;
-  std::map<std::string, std::string> claims;
+  hashmap<std::string, std::string> claims;
 };
 
 


[4/8] mesos git commit: Added implicit executor authorization to the agent operator API.

Posted by vi...@apache.org.
Added implicit executor authorization to the agent operator API.

This patch updates the agent handlers for the LAUNCH_, WAIT_,
and KILL_NESTED_CONTAINER calls of the operator API to set the
`container_id` field within the authorization object,
facilitating implicit executor authorization.

Review: https://reviews.apache.org/r/58254/


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/ecab5ff3
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/ecab5ff3
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/ecab5ff3

Branch: refs/heads/master
Commit: ecab5ff3f8c6d50dd6551f552eeb381f49eb3949
Parents: c401190
Author: Greg Mann <gr...@mesosphere.io>
Authored: Fri Apr 21 10:45:19 2017 -0700
Committer: Vinod Kone <vi...@gmail.com>
Committed: Fri Apr 21 10:45:19 2017 -0700

----------------------------------------------------------------------
 include/mesos/authorizer/authorizer.proto |  17 ++--
 src/authorizer/local/authorizer.cpp       | 109 ++++++++++++++++++++++---
 src/slave/http.cpp                        |   5 ++
 3 files changed, 113 insertions(+), 18 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/ecab5ff3/include/mesos/authorizer/authorizer.proto
----------------------------------------------------------------------
diff --git a/include/mesos/authorizer/authorizer.proto b/include/mesos/authorizer/authorizer.proto
index 0951be9..3ae5df5 100644
--- a/include/mesos/authorizer/authorizer.proto
+++ b/include/mesos/authorizer/authorizer.proto
@@ -161,18 +161,20 @@ enum Action {
   // is the entire set of flags.
   VIEW_FLAGS = 18;
 
-  // This action will always set the `ExecutorInfo`, `FrameworkInfo` fields
-  // and optionally a `CommandInfo` if available.
+  // This action will always set the `ExecutorInfo`, `FrameworkInfo`, and
+  // `ContainerID` fields and optionally a `CommandInfo` if available.
   LAUNCH_NESTED_CONTAINER = 19;
 
-  // This action will set objects of type `ExecutorInfo` and `FrameworkInfo`.
+  // This action will set objects of type `ExecutorInfo`, `FrameworkInfo`, and
+  // `ContainerID`.
   KILL_NESTED_CONTAINER = 20;
 
-  // This action will set objects of type `ExecutorInfo` and `FrameworkInfo`.
+  // This action will set objects of type `ExecutorInfo`, `FrameworkInfo`, and
+  // `ContainerID`.
   WAIT_NESTED_CONTAINER = 21;
 
-  // This action will always set the `ExecutorInfo`, `FrameworkInfo` fields
-  // and optionally a `CommandInfo` if available.
+  // This action will always set the `ExecutorInfo`, `FrameworkInfo`, and
+  // `ContainerID` fields and optionally a `CommandInfo` if available.
   LAUNCH_NESTED_CONTAINER_SESSION = 22;
 
   // This action will set objects of type `ExecutorInfo` and `FrameworkInfo`.
@@ -188,7 +190,8 @@ enum Action {
   // either allowed to change the log level or he is unauthorized.
   SET_LOG_LEVEL = 26;
 
-  // This action will set objects of type `ExecutorInfo` and `FrameworkInfo`.
+  // This action will set objects of type `ExecutorInfo`, `FrameworkInfo`, and
+  // `ContainerID`.
   REMOVE_NESTED_CONTAINER = 27;
 }
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/ecab5ff3/src/authorizer/local/authorizer.cpp
----------------------------------------------------------------------
diff --git a/src/authorizer/local/authorizer.cpp b/src/authorizer/local/authorizer.cpp
index 8c3178a..3eb59d3 100644
--- a/src/authorizer/local/authorizer.cpp
+++ b/src/authorizer/local/authorizer.cpp
@@ -335,6 +335,9 @@ public:
                      object->framework_info->has_user()) {
             aclObject.add_values(object->framework_info->user());
             aclObject.set_type(mesos::ACL::Entity::SOME);
+          } else if (object->container_id != nullptr) {
+            aclObject.add_values(object->container_id->value());
+            aclObject.set_type(mesos::ACL::Entity::SOME);
           }
 
           break;
@@ -538,6 +541,40 @@ private:
 };
 
 
+class LocalImplicitExecutorObjectApprover : public ObjectApprover
+{
+public:
+  LocalImplicitExecutorObjectApprover(const ContainerID& subject)
+    : subject_(subject) {}
+
+  // Executors are permitted to perform an action when the root ContainerID in
+  // the object is equal to the ContainerID that was extracted from the
+  // subject's claims.
+  virtual Try<bool> approved(
+      const Option<ObjectApprover::Object>& object) const noexcept override
+  {
+    return object.isSome() &&
+           object->container_id != nullptr &&
+           subject_ == protobuf::getRootContainerId(*object->container_id);
+  }
+
+private:
+  const ContainerID subject_;
+};
+
+
+// Implementation of the ObjectApprover interface denying all objects.
+class RejectingObjectApprover : public ObjectApprover
+{
+public:
+  virtual Try<bool> approved(
+      const Option<ObjectApprover::Object>& object) const noexcept override
+  {
+    return false;
+  }
+};
+
+
 class LocalAuthorizerProcess : public ProtobufProcess<LocalAuthorizerProcess>
 {
 public:
@@ -573,7 +610,12 @@ public:
 
   Future<bool> authorized(const authorization::Request& request)
   {
-    return getObjectApprover(request.subject(), request.action())
+    Option<authorization::Subject> subject;
+    if (request.has_subject()) {
+      subject = request.subject();
+    }
+
+    return getObjectApprover(subject, request.action())
       .then([=](const Owned<ObjectApprover>& objectApprover) -> Future<bool> {
         Option<ObjectApprover::Object> object = None();
         if (request.has_object()) {
@@ -645,20 +687,65 @@ public:
         acls.permissive()));
   }
 
-  Future<Owned<ObjectApprover>> getObjectApprover(
+  Future<Owned<ObjectApprover>> getImplicitExecutorObjectApprover(
       const Option<authorization::Subject>& subject,
       const authorization::Action& action)
   {
-    // Implementation of the ObjectApprover interface denying all objects.
-    class RejectingObjectApprover : public ObjectApprover
-    {
-    public:
-      virtual Try<bool> approved(
-          const Option<ObjectApprover::Object>& object) const noexcept override
-      {
-        return false;
+    CHECK(subject.isSome() &&
+          subject->has_claims() &&
+          !subject->has_value() &&
+          (action == authorization::LAUNCH_NESTED_CONTAINER ||
+           action == authorization::WAIT_NESTED_CONTAINER ||
+           action == authorization::KILL_NESTED_CONTAINER ||
+           action == authorization::LAUNCH_NESTED_CONTAINER_SESSION ||
+           action == authorization::REMOVE_NESTED_CONTAINER ||
+           action == authorization::ATTACH_CONTAINER_OUTPUT));
+
+    Option<ContainerID> subjectContainerId;
+    foreach (const Label& claim, subject->claims().labels()) {
+      if (claim.key() == "cid" && claim.has_value()) {
+        subjectContainerId = ContainerID();
+        subjectContainerId->set_value(claim.value());
+        break;
       }
-    };
+    }
+
+    if (subjectContainerId.isNone()) {
+      // If the subject's claims do not include a ContainerID,
+      // we deny all objects.
+      return Owned<ObjectApprover>(new RejectingObjectApprover());
+    }
+
+    return Owned<ObjectApprover>(new LocalImplicitExecutorObjectApprover(
+        subjectContainerId.get()));
+  }
+
+  Future<Owned<ObjectApprover>> getObjectApprover(
+      const Option<authorization::Subject>& subject,
+      const authorization::Action& action)
+  {
+    // We return the `LocalImplicitExecutorObjectApprover` only for subjects and
+    // actions which it knows how to handle. This means the subject should have
+    // claims but no value, and the action should be one of the actions used by
+    // the default executor.
+    if (subject.isSome() &&
+        subject->has_claims() &&
+        !subject->has_value() &&
+        (action == authorization::LAUNCH_NESTED_CONTAINER ||
+         action == authorization::WAIT_NESTED_CONTAINER ||
+         action == authorization::KILL_NESTED_CONTAINER ||
+         action == authorization::LAUNCH_NESTED_CONTAINER_SESSION ||
+         action == authorization::REMOVE_NESTED_CONTAINER ||
+         action == authorization::ATTACH_CONTAINER_OUTPUT)) {
+      return getImplicitExecutorObjectApprover(subject, action);
+    }
+
+    // Currently, implicit executor authorization is the only case which handles
+    // subjects that do not have the `value` field set. If the previous case was
+    // not true and `value` is not set, then we should fail all requests.
+    if (subject.isSome() && !subject->has_value()) {
+      return Owned<ObjectApprover>(new RejectingObjectApprover());
+    }
 
     if (action == authorization::LAUNCH_NESTED_CONTAINER ||
         action == authorization::LAUNCH_NESTED_CONTAINER_SESSION) {

http://git-wip-us.apache.org/repos/asf/mesos/blob/ecab5ff3/src/slave/http.cpp
----------------------------------------------------------------------
diff --git a/src/slave/http.cpp b/src/slave/http.cpp
index 468cf33..93825fa 100644
--- a/src/slave/http.cpp
+++ b/src/slave/http.cpp
@@ -2252,6 +2252,7 @@ Future<Response> Slave::Http::_launchNestedContainer(
   object.executor_info = &(executor->info);
   object.framework_info = &(framework->info);
   object.command_info = &(commandInfo);
+  object.container_id = &(containerId);
 
   Try<bool> approved = approver.get()->approved(object);
 
@@ -2340,6 +2341,7 @@ Future<Response> Slave::Http::waitNestedContainer(
       ObjectApprover::Object object;
       object.executor_info = &(executor->info);
       object.framework_info = &(framework->info);
+      object.container_id = &(containerId);
 
       Try<bool> approved = waitApprover.get()->approved(object);
 
@@ -2414,6 +2416,7 @@ Future<Response> Slave::Http::killNestedContainer(
       ObjectApprover::Object object;
       object.executor_info = &(executor->info);
       object.framework_info = &(framework->info);
+      object.container_id = &(containerId);
 
       Try<bool> approved = killApprover.get()->approved(object);
 
@@ -2473,6 +2476,7 @@ Future<Response> Slave::Http::removeNestedContainer(
       ObjectApprover::Object object;
       object.executor_info = &(executor->info);
       object.framework_info = &(framework->info);
+      object.container_id = &(containerId);
 
       Try<bool> approved = removeApprover.get()->approved(object);
 
@@ -2928,6 +2932,7 @@ Future<Response> Slave::Http::attachContainerOutput(
       ObjectApprover::Object object;
       object.executor_info = &(executor->info);
       object.framework_info = &(framework->info);
+      object.container_id = &(containerId);
 
       Try<bool> approved = attachOutputApprover.get()->approved(object);
 


[7/8] mesos git commit: Added tests for failed executor authorization.

Posted by vi...@apache.org.
Added tests for failed executor authorization.

This patch adds new tests to verify that HTTP executors cannot
subscribe or launch nested containers when HTTP executor
authentication is enabled, authorization is enabled, and they
do not provide a valid executor authentication token

Review: https://reviews.apache.org/r/58428/


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/0124cbfd
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/0124cbfd
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/0124cbfd

Branch: refs/heads/master
Commit: 0124cbfd31116262f533c0dc38bef9a60238bfbd
Parents: 33e2ee0
Author: Greg Mann <gr...@mesosphere.io>
Authored: Fri Apr 21 10:45:38 2017 -0700
Committer: Vinod Kone <vi...@gmail.com>
Committed: Fri Apr 21 10:45:38 2017 -0700

----------------------------------------------------------------------
 src/tests/slave_authorization_tests.cpp | 506 +++++++++++++++++++++++++++
 1 file changed, 506 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/0124cbfd/src/tests/slave_authorization_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/slave_authorization_tests.cpp b/src/tests/slave_authorization_tests.cpp
index aef7e36..4e55148 100644
--- a/src/tests/slave_authorization_tests.cpp
+++ b/src/tests/slave_authorization_tests.cpp
@@ -21,27 +21,41 @@
 
 #include <gtest/gtest.h>
 
+#include <mesos/authentication/secret_generator.hpp>
+
 #include <mesos/authorizer/authorizer.hpp>
 
 #include <mesos/module/authorizer.hpp>
 
+#include <process/authenticator.hpp>
 #include <process/clock.hpp>
+#include <process/future.hpp>
 #include <process/http.hpp>
 #include <process/owned.hpp>
 
+#include <stout/strings.hpp>
 #include <stout/try.hpp>
 
+#ifdef USE_SSL_SOCKET
+#include "authentication/executor/jwt_secret_generator.hpp"
+#endif // USE_SSL_SOCKET
+
 #include "authorizer/local/authorizer.hpp"
 
 #include "master/detector/standalone.hpp"
 
 #include "tests/containerizer.hpp"
 #include "tests/mesos.hpp"
+#include "tests/mock_slave.hpp"
 #include "tests/module.hpp"
 #include "tests/resources_utils.hpp"
 
 namespace http = process::http;
 
+#ifdef USE_SSL_SOCKET
+using mesos::authentication::executor::JWTSecretGenerator;
+#endif // USE_SSL_SOCKET
+
 using mesos::internal::master::Master;
 
 using mesos::internal::slave::Slave;
@@ -52,11 +66,14 @@ using mesos::master::detector::StandaloneMasterDetector;
 using process::Clock;
 using process::Future;
 using process::Owned;
+using process::Promise;
 
 using process::http::Forbidden;
 using process::http::OK;
 using process::http::Response;
 
+using process::http::authentication::Principal;
+
 using std::string;
 using std::vector;
 
@@ -563,6 +580,495 @@ TEST_F(ExecutorAuthorizationTest, RunTaskGroup)
   driver.stop();
   driver.join();
 }
+
+
+// This test verifies that default executor subscription fails if the executor
+// provides a properly-signed authentication token with invalid claims.
+TEST_F(ExecutorAuthorizationTest, FailedSubscribe)
+{
+  Try<Owned<cluster::Master>> master = StartMaster();
+  ASSERT_SOME(master);
+
+  // Start an agent with permissive ACLs so that a task can be launched.
+  ACLs acls;
+  acls.set_permissive(true);
+
+  Result<Authorizer*> authorizer = Authorizer::create(acls);
+  ASSERT_SOME(authorizer);
+
+  slave::Flags flags = CreateSlaveFlags();
+  flags.acls = acls;
+
+  Owned<MasterDetector> detector = master.get()->createDetector();
+
+  auto executor = std::make_shared<v1::MockHTTPExecutor>();
+
+  v1::Resources resources =
+    v1::Resources::parse("cpus:0.1;mem:32;disk:32").get();
+
+  v1::ExecutorInfo executorInfo;
+  executorInfo.set_type(v1::ExecutorInfo::DEFAULT);
+  executorInfo.mutable_executor_id()->CopyFrom(v1::DEFAULT_EXECUTOR_ID);
+  executorInfo.mutable_resources()->CopyFrom(resources);
+
+  Owned<TestContainerizer> containerizer(
+      new TestContainerizer(devolve(executorInfo.executor_id()), executor));
+
+  // This pointer is passed to the agent, which will perform the cleanup.
+  MockSecretGenerator* mockSecretGenerator = new MockSecretGenerator();
+
+  MockSlave slave(
+      flags,
+      detector.get(),
+      containerizer.get(),
+      None(),
+      authorizer.get(),
+      mockSecretGenerator);
+  spawn(slave);
+
+  auto scheduler = std::make_shared<v1::MockHTTPScheduler>();
+
+  Future<Nothing> connected;
+  EXPECT_CALL(*scheduler, connected(_))
+    .WillOnce(FutureSatisfy(&connected));
+
+  v1::scheduler::TestMesos mesos(
+      master.get()->pid,
+      ContentType::PROTOBUF,
+      scheduler);
+
+  AWAIT_READY(connected);
+
+  Future<v1::scheduler::Event::Subscribed> subscribed;
+  EXPECT_CALL(*scheduler, subscribed(_, _))
+    .WillOnce(FutureArg<1>(&subscribed));
+
+  Future<v1::scheduler::Event::Offers> offers;
+  EXPECT_CALL(*scheduler, offers(_, _))
+    .WillOnce(FutureArg<1>(&offers))
+    .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+  EXPECT_CALL(*scheduler, heartbeat(_))
+    .WillRepeatedly(Return()); // Ignore heartbeats.
+
+  {
+    v1::scheduler::Call call;
+    call.set_type(v1::scheduler::Call::SUBSCRIBE);
+    v1::scheduler::Call::Subscribe* subscribe = call.mutable_subscribe();
+    subscribe->mutable_framework_info()->CopyFrom(v1::DEFAULT_FRAMEWORK_INFO);
+
+    mesos.send(call);
+  }
+
+  AWAIT_READY(subscribed);
+  v1::FrameworkID frameworkId(subscribed->framework_id());
+
+  executorInfo.mutable_framework_id()->CopyFrom(frameworkId);
+
+  AWAIT_READY(offers);
+  EXPECT_NE(0, offers->offers().size());
+
+  Future<v1::executor::Mesos*> executorLib;
+  EXPECT_CALL(*executor, connected(_))
+    .WillOnce(FutureArg<0>(&executorLib));
+
+  Owned<JWTSecretGenerator> jwtSecretGenerator(
+      new JWTSecretGenerator(DEFAULT_EXECUTOR_SECRET_KEY));
+
+  // Create a principal which contains an incorrect ContainerID.
+  hashmap<string, string> claims;
+  claims["fid"] = frameworkId.value();
+  claims["eid"] = v1::DEFAULT_EXECUTOR_ID.value();
+  claims["cid"] = UUID::random().toString();
+
+  Principal principal(None(), claims);
+
+  // Generate an authentication token which is signed using the correct key,
+  // but contains an invalid set of claims.
+  Future<Secret> authenticationToken =
+    jwtSecretGenerator->generate(principal);
+
+  AWAIT_READY(authenticationToken);
+
+  EXPECT_CALL(*mockSecretGenerator, generate(_))
+    .WillOnce(Return(authenticationToken.get()));
+
+  const v1::Offer& offer = offers->offers(0);
+  const v1::AgentID& agentId = offer.agent_id();
+
+  {
+    v1::TaskInfo taskInfo =
+      v1::createTask(agentId, resources, SLEEP_COMMAND(1000));
+
+    v1::TaskGroupInfo taskGroup;
+    taskGroup.add_tasks()->CopyFrom(taskInfo);
+
+    v1::scheduler::Call call;
+    call.mutable_framework_id()->CopyFrom(frameworkId);
+    call.set_type(v1::scheduler::Call::ACCEPT);
+
+    v1::scheduler::Call::Accept* accept = call.mutable_accept();
+    accept->add_offer_ids()->CopyFrom(offer.id());
+
+    v1::Offer::Operation* operation = accept->add_operations();
+    operation->set_type(v1::Offer::Operation::LAUNCH_GROUP);
+
+    v1::Offer::Operation::LaunchGroup* launchGroup =
+      operation->mutable_launch_group();
+
+    launchGroup->mutable_executor()->CopyFrom(executorInfo);
+    launchGroup->mutable_task_group()->CopyFrom(taskGroup);
+
+    mesos.send(call);
+  }
+
+  AWAIT_READY(executorLib);
+
+  {
+    v1::executor::Call call;
+    call.mutable_framework_id()->CopyFrom(frameworkId);
+    call.mutable_executor_id()->CopyFrom(v1::DEFAULT_EXECUTOR_ID);
+
+    call.set_type(v1::executor::Call::SUBSCRIBE);
+
+    call.mutable_subscribe();
+
+    executorLib.get()->send(call);
+  }
+
+  Future<v1::executor::Event::Error> error;
+  EXPECT_CALL(*executor, error(_, _))
+    .WillOnce(FutureArg<1>(&error));
+
+  AWAIT_READY(error);
+  EXPECT_EQ(
+      error->message(),
+      "Received unexpected '403 Forbidden' () for SUBSCRIBE");
+
+  terminate(slave);
+  wait(slave);
+}
+
+
+// This test verifies that executor API and operator API calls receive an
+// unsuccessful response if the request contains a properly-signed
+// authentication token with invalid claims.
+TEST_F(ExecutorAuthorizationTest, FailedApiCalls)
+{
+  Try<Owned<cluster::Master>> master = StartMaster();
+  ASSERT_SOME(master);
+
+  // Start an agent with permissive ACLs so that a task can be launched and the
+  // local authorizer's implicit executor authorization will be performed.
+  ACLs acls;
+  acls.set_permissive(true);
+
+  slave::Flags flags = CreateSlaveFlags();
+  flags.acls = acls;
+
+  Owned<MasterDetector> detector = master.get()->createDetector();
+
+  v1::Resources resources =
+    v1::Resources::parse("cpus:0.1;mem:32;disk:32").get();
+
+  v1::ExecutorInfo executorInfo;
+  executorInfo.set_type(v1::ExecutorInfo::DEFAULT);
+  executorInfo.mutable_executor_id()->CopyFrom(v1::DEFAULT_EXECUTOR_ID);
+  executorInfo.mutable_resources()->CopyFrom(resources);
+
+  auto executor = std::make_shared<v1::MockHTTPExecutor>();
+
+  Owned<TestContainerizer> containerizer(new TestContainerizer(
+      devolve(executorInfo.executor_id()), executor));
+
+  Try<Owned<cluster::Slave>> slave =
+    this->StartSlave(detector.get(), containerizer.get(), flags);
+  ASSERT_SOME(slave);
+
+  auto scheduler = std::make_shared<v1::MockHTTPScheduler>();
+
+  Future<Nothing> connected;
+  EXPECT_CALL(*scheduler, connected(_))
+    .WillOnce(FutureSatisfy(&connected));
+
+  v1::scheduler::TestMesos mesos(
+      master.get()->pid,
+      ContentType::PROTOBUF,
+      scheduler);
+
+  AWAIT_READY(connected);
+
+  Future<v1::scheduler::Event::Subscribed> frameworkSubscribed;
+  EXPECT_CALL(*scheduler, subscribed(_, _))
+    .WillOnce(FutureArg<1>(&frameworkSubscribed));
+
+  Future<v1::scheduler::Event::Offers> offers;
+  EXPECT_CALL(*scheduler, offers(_, _))
+    .WillOnce(FutureArg<1>(&offers))
+    .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+  EXPECT_CALL(*scheduler, heartbeat(_))
+    .WillRepeatedly(Return()); // Ignore heartbeats.
+
+  {
+    v1::scheduler::Call call;
+    call.set_type(v1::scheduler::Call::SUBSCRIBE);
+    v1::scheduler::Call::Subscribe* subscribe = call.mutable_subscribe();
+    subscribe->mutable_framework_info()->CopyFrom(v1::DEFAULT_FRAMEWORK_INFO);
+
+    mesos.send(call);
+  }
+
+  AWAIT_READY(frameworkSubscribed);
+  v1::FrameworkID frameworkId(frameworkSubscribed->framework_id());
+
+  executorInfo.mutable_framework_id()->CopyFrom(frameworkId);
+
+  AWAIT_READY(offers);
+  EXPECT_NE(0, offers->offers().size());
+
+  Future<v1::executor::Mesos*> executorLib;
+  EXPECT_CALL(*executor, connected(_))
+    .WillOnce(FutureArg<0>(&executorLib));
+
+  const v1::Offer& offer = offers->offers(0);
+  const v1::AgentID& agentId = offer.agent_id();
+
+  {
+    v1::scheduler::Call call;
+    call.mutable_framework_id()->CopyFrom(frameworkId);
+    call.set_type(v1::scheduler::Call::ACCEPT);
+
+    v1::scheduler::Call::Accept* accept = call.mutable_accept();
+    accept->add_offer_ids()->CopyFrom(offer.id());
+
+    v1::Offer::Operation* operation = accept->add_operations();
+    operation->set_type(v1::Offer::Operation::LAUNCH_GROUP);
+
+    v1::TaskInfo taskInfo =
+      v1::createTask(agentId, resources, SLEEP_COMMAND(1000));
+
+    v1::TaskGroupInfo taskGroup;
+    taskGroup.add_tasks()->CopyFrom(taskInfo);
+
+    v1::Offer::Operation::LaunchGroup* launchGroup =
+      operation->mutable_launch_group();
+
+    launchGroup->mutable_executor()->CopyFrom(executorInfo);
+    launchGroup->mutable_task_group()->CopyFrom(taskGroup);
+
+    mesos.send(call);
+  }
+
+  AWAIT_READY(executorLib);
+
+  Future<v1::executor::Event::Subscribed> executorSubscribed;
+  EXPECT_CALL(*executor, subscribed(_, _))
+    .WillOnce(FutureArg<1>(&executorSubscribed));
+
+  Future<Nothing> launchGroup;
+  EXPECT_CALL(*executor, launchGroup(_, _))
+    .WillOnce(FutureSatisfy(&launchGroup));
+
+  {
+    v1::executor::Call call;
+    call.mutable_framework_id()->CopyFrom(frameworkId);
+    call.mutable_executor_id()->CopyFrom(v1::DEFAULT_EXECUTOR_ID);
+
+    call.set_type(v1::executor::Call::SUBSCRIBE);
+
+    call.mutable_subscribe();
+
+    executorLib.get()->send(call);
+  }
+
+  // Wait for the executor to subscribe. Once it is in the SUBSCRIBED state,
+  // the UPDATE and MESSAGE executor calls can be attempted.
+  AWAIT_READY(executorSubscribed);
+  AWAIT_READY(launchGroup);
+
+  // Create a principal which contains an incorrect ContainerID.
+  hashmap<string, string> claims;
+  claims["fid"] = frameworkId.value();
+  claims["eid"] = v1::DEFAULT_EXECUTOR_ID.value();
+  claims["cid"] = UUID::random().toString();
+
+  Principal incorrectPrincipal(None(), claims);
+
+  // Generate an authentication token which is signed using the correct key,
+  // but contains an invalid set of claims.
+  Owned<JWTSecretGenerator> jwtSecretGenerator(
+      new JWTSecretGenerator(DEFAULT_EXECUTOR_SECRET_KEY));
+
+  Future<Secret> authenticationToken =
+    jwtSecretGenerator->generate(incorrectPrincipal);
+
+  AWAIT_READY(authenticationToken);
+
+  v1::ContainerID containerId;
+  containerId.set_value(UUID::random().toString());
+  containerId.mutable_parent()->CopyFrom(executorSubscribed->container_id());
+
+  http::Headers headers;
+  headers["Authorization"] =
+    "Bearer " + authenticationToken.get().value().data();
+
+  // Since the executor library has already been initialized with a valid
+  // authentication token, we use an HTTP helper function to send the
+  // executor API and operator API calls with an invalid token.
+
+  {
+    v1::agent::Call call;
+    call.set_type(v1::agent::Call::LAUNCH_NESTED_CONTAINER);
+
+    call.mutable_launch_nested_container()->mutable_container_id()
+      ->CopyFrom(containerId);
+
+    Future<http::Response> response = http::post(
+      slave.get()->pid,
+      "api/v1",
+      headers,
+      serialize(ContentType::PROTOBUF, call),
+      stringify(ContentType::PROTOBUF));
+
+    AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::Forbidden().status, response);
+  }
+
+  {
+    v1::agent::Call call;
+    call.set_type(v1::agent::Call::LAUNCH_NESTED_CONTAINER_SESSION);
+
+    call.mutable_launch_nested_container_session()->mutable_container_id()
+      ->CopyFrom(containerId);
+    call.mutable_launch_nested_container_session()->mutable_command()
+      ->set_value("sleep 120");
+
+    Future<http::Response> response = http::post(
+      slave.get()->pid,
+      "api/v1",
+      headers,
+      serialize(ContentType::PROTOBUF, call),
+      stringify(ContentType::PROTOBUF));
+
+    AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::Forbidden().status, response);
+  }
+
+  {
+    v1::agent::Call call;
+    call.set_type(v1::agent::Call::WAIT_NESTED_CONTAINER);
+
+    call.mutable_wait_nested_container()->mutable_container_id()
+      ->CopyFrom(containerId);
+
+    Future<http::Response> response = http::post(
+      slave.get()->pid,
+      "api/v1",
+      headers,
+      serialize(ContentType::PROTOBUF, call),
+      stringify(ContentType::PROTOBUF));
+
+    AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::Forbidden().status, response);
+  }
+
+  {
+    v1::agent::Call call;
+    call.set_type(v1::agent::Call::KILL_NESTED_CONTAINER);
+
+    call.mutable_kill_nested_container()->mutable_container_id()
+      ->CopyFrom(containerId);
+
+    Future<http::Response> response = http::post(
+      slave.get()->pid,
+      "api/v1",
+      headers,
+      serialize(ContentType::PROTOBUF, call),
+      stringify(ContentType::PROTOBUF));
+
+    AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::Forbidden().status, response);
+  }
+
+  {
+    v1::agent::Call call;
+    call.set_type(v1::agent::Call::REMOVE_NESTED_CONTAINER);
+
+    call.mutable_remove_nested_container()->mutable_container_id()
+      ->CopyFrom(containerId);
+
+    Future<http::Response> response = http::post(
+      slave.get()->pid,
+      "api/v1",
+      headers,
+      serialize(ContentType::PROTOBUF, call),
+      stringify(ContentType::PROTOBUF));
+
+    AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::Forbidden().status, response);
+  }
+
+  {
+    v1::agent::Call call;
+    call.set_type(v1::agent::Call::ATTACH_CONTAINER_OUTPUT);
+
+    call.mutable_attach_container_output()->mutable_container_id()
+      ->CopyFrom(containerId);
+
+    Future<http::Response> response = http::post(
+      slave.get()->pid,
+      "api/v1",
+      headers,
+      serialize(ContentType::PROTOBUF, call),
+      stringify(ContentType::PROTOBUF));
+
+    AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::Forbidden().status, response);
+  }
+
+  const string failureMessage =
+    "does not contain a 'cid' claim with the correct active ContainerID";
+
+  {
+    v1::TaskStatus status;
+    status.mutable_task_id()->set_value(UUID::random().toString());
+    status.set_state(v1::TASK_RUNNING);
+    status.set_uuid(UUID::random().toBytes());
+    status.set_source(v1::TaskStatus::SOURCE_EXECUTOR);
+
+    v1::executor::Call call;
+    call.set_type(v1::executor::Call::UPDATE);
+    call.mutable_framework_id()->CopyFrom(frameworkId);
+    call.mutable_executor_id()->CopyFrom(v1::DEFAULT_EXECUTOR_ID);
+    call.mutable_update()->mutable_status()->CopyFrom(status);
+
+    Future<http::Response> response = http::post(
+      slave.get()->pid,
+      "api/v1/executor",
+      headers,
+      serialize(ContentType::PROTOBUF, call),
+      stringify(ContentType::PROTOBUF));
+
+    AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::Forbidden().status, response);
+    EXPECT_TRUE(strings::contains(response->body, failureMessage));
+  }
+
+  {
+    v1::executor::Call call;
+    call.set_type(v1::executor::Call::MESSAGE);
+    call.mutable_framework_id()->CopyFrom(frameworkId);
+    call.mutable_executor_id()->CopyFrom(v1::DEFAULT_EXECUTOR_ID);
+    call.mutable_message()->set_data("executor message");
+
+    Future<http::Response> response = http::post(
+      slave.get()->pid,
+      "api/v1/executor",
+      headers,
+      serialize(ContentType::PROTOBUF, call),
+      stringify(ContentType::PROTOBUF));
+
+    AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::Forbidden().status, response);
+    EXPECT_TRUE(strings::contains(response->body, failureMessage));
+  }
+
+  EXPECT_CALL(*executor, shutdown(_))
+    .Times(AtMost(1));
+}
 #endif // USE_SSL_SOCKET
 
 


[2/8] mesos git commit: Allowed the local authorizer to accept subjects with no value.

Posted by vi...@apache.org.
Allowed the local authorizer to accept subjects with no value.

This patch updates checks in the local authorizer to allow subjects
which specify `claims` instead of a `value`.

Review: https://reviews.apache.org/r/58252/


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/02c2d6ff
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/02c2d6ff
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/02c2d6ff

Branch: refs/heads/master
Commit: 02c2d6ff5ca6cd64f33693ab52b7dfed899143d9
Parents: 6d06388
Author: Greg Mann <gr...@mesosphere.io>
Authored: Fri Apr 21 10:45:13 2017 -0700
Committer: Vinod Kone <vi...@gmail.com>
Committed: Fri Apr 21 10:45:13 2017 -0700

----------------------------------------------------------------------
 src/authorizer/local/authorizer.cpp | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/02c2d6ff/src/authorizer/local/authorizer.cpp
----------------------------------------------------------------------
diff --git a/src/authorizer/local/authorizer.cpp b/src/authorizer/local/authorizer.cpp
index 1c1f912..8ca9ae9 100644
--- a/src/authorizer/local/authorizer.cpp
+++ b/src/authorizer/local/authorizer.cpp
@@ -1058,10 +1058,11 @@ process::Future<bool> LocalAuthorizer::authorized(
   const authorization::Request& request)
 {
   // Request sanity checks.
-  // A set `subject` should always come with a set `value`.
+  // A set `subject` should always come with a set `value` or `claims`.
   CHECK(
     !request.has_subject() ||
-    (request.has_subject() && request.subject().has_value()));
+    request.subject().has_value() ||
+    request.subject().has_claims());
 
   // A set `action` is mandatory.
   CHECK(request.has_action());


[8/8] mesos git commit: Enabled authorization in default executor check tests.

Posted by vi...@apache.org.
Enabled authorization in default executor check tests.

This patch enables authorization in the check and health check
tests which use the default executor. Simple permissive ACLs are
set, forcing the local authorizer to be loaded which allows us
to test the implicit executor authorization code for agent
operator API calls.

Review: https://reviews.apache.org/r/58458/


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/3f5b2c90
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/3f5b2c90
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/3f5b2c90

Branch: refs/heads/master
Commit: 3f5b2c90c2f6825148662db8e1e88be08f37f3b5
Parents: 0124cbf
Author: Greg Mann <gr...@mesosphere.io>
Authored: Fri Apr 21 10:45:41 2017 -0700
Committer: Vinod Kone <vi...@gmail.com>
Committed: Fri Apr 21 10:45:41 2017 -0700

----------------------------------------------------------------------
 src/tests/check_tests.cpp        | 7 +++++++
 src/tests/health_check_tests.cpp | 7 +++++++
 2 files changed, 14 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/3f5b2c90/src/tests/check_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/check_tests.cpp b/src/tests/check_tests.cpp
index 79ba5eb..72fa64c 100644
--- a/src/tests/check_tests.cpp
+++ b/src/tests/check_tests.cpp
@@ -891,6 +891,13 @@ protected:
     // executors to authenticate with the agent operator API if Mesos was not
     // built with SSL support.
     flags.authenticate_http_readwrite = false;
+
+    // Set permissive ACLs in the agent so that the local authorizer will be
+    // loaded and implicit executor authorization will be tested.
+    ACLs acls;
+    acls.set_permissive(true);
+
+    flags.acls = acls;
 #endif // USE_SSL_SOCKET
 
     return flags;

http://git-wip-us.apache.org/repos/asf/mesos/blob/3f5b2c90/src/tests/health_check_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/health_check_tests.cpp b/src/tests/health_check_tests.cpp
index 3bba1bb..f5375ac 100644
--- a/src/tests/health_check_tests.cpp
+++ b/src/tests/health_check_tests.cpp
@@ -2153,6 +2153,13 @@ TEST_F_TEMP_DISABLED_ON_WINDOWS(
   // executors to authenticate with the agent operator API if Mesos was not
   // built with SSL support.
   flags.authenticate_http_readwrite = false;
+
+  // Set permissive ACLs in the agent so that the local authorizer will be
+  // loaded and implicit executor authorization will be tested.
+  ACLs acls;
+  acls.set_permissive(true);
+
+  flags.acls = acls;
 #endif // USE_SSL_SOCKET
 
   Fetcher fetcher;


[3/8] mesos git commit: Added a ContainerID to 'ObjectApprover::Object'.

Posted by vi...@apache.org.
Added a ContainerID to 'ObjectApprover::Object'.

This patch adds a new member, `container_id` to the
`ObjectApprover::Object` to facilitate implicit executor
authorization.

Review: https://reviews.apache.org/r/58253/


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/c4011900
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/c4011900
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/c4011900

Branch: refs/heads/master
Commit: c401190086e1a419280c49833663aaf740ce6104
Parents: 02c2d6f
Author: Greg Mann <gr...@mesosphere.io>
Authored: Fri Apr 21 10:45:16 2017 -0700
Committer: Vinod Kone <vi...@gmail.com>
Committed: Fri Apr 21 10:45:16 2017 -0700

----------------------------------------------------------------------
 include/mesos/authorizer/authorizer.hpp   | 8 ++++++--
 include/mesos/authorizer/authorizer.proto | 1 +
 src/authorizer/local/authorizer.cpp       | 1 +
 3 files changed, 8 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/c4011900/include/mesos/authorizer/authorizer.hpp
----------------------------------------------------------------------
diff --git a/include/mesos/authorizer/authorizer.hpp b/include/mesos/authorizer/authorizer.hpp
index 75801cc..4a7376f 100644
--- a/include/mesos/authorizer/authorizer.hpp
+++ b/include/mesos/authorizer/authorizer.hpp
@@ -54,7 +54,8 @@ public:
         quota_info(nullptr),
         weight_info(nullptr),
         resource(nullptr),
-        command_info(nullptr) {}
+        command_info(nullptr),
+        container_id(nullptr) {}
 
     Object(const authorization::Object& object)
       : value(object.has_value() ? &object.value() : nullptr),
@@ -68,7 +69,9 @@ public:
         weight_info(object.has_weight_info() ? &object.weight_info() : nullptr),
         resource(object.has_resource() ? &object.resource() : nullptr),
         command_info(
-            object.has_command_info() ? &object.command_info() : nullptr) {}
+            object.has_command_info() ? &object.command_info() : nullptr),
+        container_id(
+            object.has_container_id() ? &object.container_id() : nullptr) {}
 
     const std::string* value;
     const FrameworkInfo* framework_info;
@@ -79,6 +82,7 @@ public:
     const WeightInfo* weight_info;
     const Resource* resource;
     const CommandInfo* command_info;
+    const ContainerID* container_id;
   };
 
   /**

http://git-wip-us.apache.org/repos/asf/mesos/blob/c4011900/include/mesos/authorizer/authorizer.proto
----------------------------------------------------------------------
diff --git a/include/mesos/authorizer/authorizer.proto b/include/mesos/authorizer/authorizer.proto
index 736f76d..0951be9 100644
--- a/include/mesos/authorizer/authorizer.proto
+++ b/include/mesos/authorizer/authorizer.proto
@@ -54,6 +54,7 @@ message Object {
   optional WeightInfo weight_info = 7;
   optional Resource resource = 8;
   optional CommandInfo command_info = 9;
+  optional ContainerID container_id = 10;
 }
 
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/c4011900/src/authorizer/local/authorizer.cpp
----------------------------------------------------------------------
diff --git a/src/authorizer/local/authorizer.cpp b/src/authorizer/local/authorizer.cpp
index 8ca9ae9..8c3178a 100644
--- a/src/authorizer/local/authorizer.cpp
+++ b/src/authorizer/local/authorizer.cpp
@@ -1079,6 +1079,7 @@ process::Future<bool> LocalAuthorizer::authorized(
       request.object().has_executor_info() ||
       request.object().has_quota_info() ||
       request.object().has_weight_info() ||
+      request.object().has_container_id() ||
       request.object().has_resource())));
 
   typedef Future<bool> (LocalAuthorizerProcess::*F)(


[6/8] mesos git commit: Added a new agent authorization test which runs a task group.

Posted by vi...@apache.org.
Added a new agent authorization test which runs a task group.

This patch adds a new test,
`SlaveAuthorizerTest.AuthorizeRunTaskGroup`, which
verifies that task groups can be launched when
executor authentication is required and the local
authorizer is loaded.

Review: https://reviews.apache.org/r/58258/


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/33e2ee09
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/33e2ee09
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/33e2ee09

Branch: refs/heads/master
Commit: 33e2ee09b8ceb53cb1e64eb2dad5802e45130c3e
Parents: ccb102f
Author: Greg Mann <gr...@mesosphere.io>
Authored: Fri Apr 21 10:45:28 2017 -0700
Committer: Vinod Kone <vi...@gmail.com>
Committed: Fri Apr 21 10:45:28 2017 -0700

----------------------------------------------------------------------
 src/tests/slave_authorization_tests.cpp | 87 ++++++++++++++++++++++++++++
 1 file changed, 87 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/33e2ee09/src/tests/slave_authorization_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/slave_authorization_tests.cpp b/src/tests/slave_authorization_tests.cpp
index 3657e0a..aef7e36 100644
--- a/src/tests/slave_authorization_tests.cpp
+++ b/src/tests/slave_authorization_tests.cpp
@@ -38,6 +38,7 @@
 #include "tests/containerizer.hpp"
 #include "tests/mesos.hpp"
 #include "tests/module.hpp"
+#include "tests/resources_utils.hpp"
 
 namespace http = process::http;
 
@@ -479,6 +480,92 @@ TYPED_TEST(SlaveAuthorizerTest, AuthorizeRunTaskOnAgent)
 }
 
 
+
+// Since executor authentication currently has SSL as a dependency, it does not
+// make sense to test executor authorization when Mesos has not been built with
+// SSL. In that case, no executor principal will be available on which to
+// perform authorization, so we disable the following tests.
+#ifdef USE_SSL_SOCKET
+class ExecutorAuthorizationTest : public MesosTest {};
+
+
+// This test verifies that a task group is launched on the agent if the executor
+// provides a valid authentication token specifying its own ContainerID.
+TEST_F(ExecutorAuthorizationTest, RunTaskGroup)
+{
+  Try<Owned<cluster::Master>> master = StartMaster();
+  ASSERT_SOME(master);
+
+  // Start an agent with permissive ACLs so that a task can be launched.
+  ACLs acls;
+  acls.set_permissive(true);
+
+  slave::Flags flags = CreateSlaveFlags();
+  flags.acls = acls;
+
+  Owned<MasterDetector> detector = master.get()->createDetector();
+  Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), flags);
+  ASSERT_SOME(slave);
+
+  FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO;
+
+  MockScheduler sched;
+  MesosSchedulerDriver driver(
+      &sched, frameworkInfo, master.get()->pid, DEFAULT_CREDENTIAL);
+
+  Future<FrameworkID> frameworkId;
+  EXPECT_CALL(sched, registered(&driver, _, _))
+    .WillOnce(FutureArg<1>(&frameworkId));
+
+  Future<vector<Offer>> offers;
+  EXPECT_CALL(sched, resourceOffers(&driver, _))
+    .WillOnce(FutureArg<1>(&offers))
+    .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+  driver.start();
+
+  AWAIT_READY(frameworkId);
+
+  AWAIT_READY(offers);
+  EXPECT_FALSE(offers.get().empty());
+
+  Offer offer = offers.get()[0];
+
+  TaskInfo task = createTask(
+      offer.slave_id(),
+      Resources::parse("cpus:0.5;mem:32").get(),
+      "sleep 1000");
+
+  Future<TaskStatus> status;
+
+  EXPECT_CALL(sched, statusUpdate(&driver, _))
+    .WillOnce(FutureArg<1>(&status));
+
+  Resources executorResources =
+    allocatedResources(Resources::parse("cpus:0.1;mem:32;disk:32").get(), "*");
+
+  ExecutorInfo executor;
+  executor.mutable_executor_id()->set_value("default");
+  executor.set_type(ExecutorInfo::DEFAULT);
+  executor.mutable_framework_id()->CopyFrom(frameworkId.get());
+  executor.mutable_resources()->CopyFrom(executorResources);
+
+  TaskGroupInfo taskGroup;
+  taskGroup.add_tasks()->CopyFrom(task);
+
+  driver.acceptOffers({offer.id()}, {LAUNCH_GROUP(executor, taskGroup)});
+
+  AWAIT_READY(status);
+
+  ASSERT_EQ(task.task_id(), status->task_id());
+  EXPECT_EQ(TASK_RUNNING, status->state());
+
+  driver.stop();
+  driver.join();
+}
+#endif // USE_SSL_SOCKET
+
+
 // Parameterized fixture for agent-specific authorization tests. The
 // path of the tested endpoint is passed as the only parameter.
 class SlaveEndpointTest:


[5/8] mesos git commit: Added implicit authorization to the agent executor API.

Posted by vi...@apache.org.
Added implicit authorization to the agent executor API.

This patch updates the agent handler for the executor API to
verify the FrameworkID and ExecutorID contained within the
executor's `Principal`, if present. This effectively performs
implicit authorization of executor calls.

Review: https://reviews.apache.org/r/58255/


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/ccb102f2
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/ccb102f2
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/ccb102f2

Branch: refs/heads/master
Commit: ccb102f212d38cd5ad2bb5ce848b8ebe7629b6ba
Parents: ecab5ff
Author: Greg Mann <gr...@mesosphere.io>
Authored: Fri Apr 21 10:45:24 2017 -0700
Committer: Vinod Kone <vi...@gmail.com>
Committed: Fri Apr 21 10:45:24 2017 -0700

----------------------------------------------------------------------
 src/slave/http.cpp | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 48 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/ccb102f2/src/slave/http.cpp
----------------------------------------------------------------------
diff --git a/src/slave/http.cpp b/src/slave/http.cpp
index 93825fa..2ce345d 100644
--- a/src/slave/http.cpp
+++ b/src/slave/http.cpp
@@ -619,6 +619,40 @@ string Slave::Http::EXECUTOR_HELP() {
 }
 
 
+// TODO(greggomann): Remove this function when implicit executor authorization
+// is moved into the authorizer. See MESOS-7399.
+Option<Error> verifyExecutorClaims(
+    const Principal& principal,
+    const FrameworkID& frameworkId,
+    const ExecutorID& executorId,
+    const ContainerID& containerId) {
+  if (!(principal.claims.contains("fid") &&
+        principal.claims.at("fid") == frameworkId.value())) {
+    return Error(
+        "Authenticated principal '" + stringify(principal) + "' does not "
+        "contain an 'fid' claim with the framework ID " +
+        stringify(frameworkId) + ", which is set in the call");
+  }
+
+  if (!(principal.claims.contains("eid") &&
+        principal.claims.at("eid") == executorId.value())) {
+    return Error(
+        "Authenticated principal '" + stringify(principal) + "' does not "
+        "contain an 'eid' claim with the executor ID " +
+        stringify(executorId) + ", which is set in the call");
+  }
+
+  if (!(principal.claims.contains("cid") &&
+        principal.claims.at("cid") == containerId.value())) {
+    return Error(
+        "Authenticated principal '" + stringify(principal) + "' does not "
+        "contain a 'cid' claim with the correct active ContainerID");
+  }
+
+  return None();
+}
+
+
 Future<Response> Slave::Http::executor(
     const Request& request,
     const Option<Principal>& principal) const
@@ -707,6 +741,20 @@ Future<Response> Slave::Http::executor(
     return BadRequest("Executor cannot be found");
   }
 
+  // TODO(greggomann): Move this implicit executor authorization
+  // into the authorizer. See MESOS-7399.
+  if (principal.isSome()) {
+    error = verifyExecutorClaims(
+        principal.get(),
+        call.framework_id(),
+        call.executor_id(),
+        executor->containerId);
+
+    if (error.isSome()) {
+      return Forbidden(error->message);
+    }
+  }
+
   if (executor->state == Executor::REGISTERING &&
       call.type() != executor::Call::SUBSCRIBE) {
     return Forbidden("Executor is not subscribed");