You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by ti...@apache.org on 2015/12/18 21:01:59 UTC

[1/7] mesos git commit: Quota: Added "SetQuota" message to ACL protobuf.

Repository: mesos
Updated Branches:
  refs/heads/master aa497e81c -> d13b65e1b


Quota: Added "SetQuota" message to ACL protobuf.

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


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

Branch: refs/heads/master
Commit: 0f2bd3bb72457298f73e3cea09fc2ad3676b715a
Parents: aa497e8
Author: Jan Schlicht <ja...@mesosphere.io>
Authored: Fri Dec 18 17:43:15 2015 +0100
Committer: Till Toenshoff <to...@me.com>
Committed: Fri Dec 18 17:48:56 2015 +0100

----------------------------------------------------------------------
 include/mesos/authorizer/authorizer.proto | 10 ++++++++++
 1 file changed, 10 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/0f2bd3bb/include/mesos/authorizer/authorizer.proto
----------------------------------------------------------------------
diff --git a/include/mesos/authorizer/authorizer.proto b/include/mesos/authorizer/authorizer.proto
index 7f981e6..7b729e1 100644
--- a/include/mesos/authorizer/authorizer.proto
+++ b/include/mesos/authorizer/authorizer.proto
@@ -107,6 +107,15 @@ message ACL {
     // Objects: Principal of the entity that created the volume.
     required Entity creator_principals = 2;
   }
+
+  // Which principals are authorized to set quotas for given roles.
+  message SetQuota {
+    // Subjects: Operator username.
+    required Entity principals = 1;
+
+    // Objects: The list of roles for which a quota can be set.
+    required Entity roles = 2;
+  }
 }
 
 
@@ -138,4 +147,5 @@ message ACLs {
   repeated ACL.UnreserveResources unreserve_resources = 6;
   repeated ACL.CreateVolume create_volumes = 7;
   repeated ACL.DestroyVolume destroy_volumes = 8;
+  repeated ACL.SetQuota set_quotas = 9;
 }


[5/7] mesos git commit: Quota: Added authentication, authorization tests.

Posted by ti...@apache.org.
Quota: Added authentication, authorization tests.

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


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

Branch: refs/heads/master
Commit: 38a18c4df2602121447a3f4ec89373465c094857
Parents: 50bf193
Author: Jan Schlicht <ja...@mesosphere.io>
Authored: Fri Dec 18 17:45:32 2015 +0100
Committer: Till Toenshoff <to...@me.com>
Committed: Fri Dec 18 20:52:30 2015 +0100

----------------------------------------------------------------------
 src/tests/authorization_tests.cpp |  78 ++++++++++++++
 src/tests/master_quota_tests.cpp  | 192 ++++++++++++++++++++++++++++++++-
 2 files changed, 269 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/38a18c4d/src/tests/authorization_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/authorization_tests.cpp b/src/tests/authorization_tests.cpp
index dcf348a..1d11a02 100644
--- a/src/tests/authorization_tests.cpp
+++ b/src/tests/authorization_tests.cpp
@@ -683,6 +683,84 @@ TYPED_TEST(AuthorizationTest, DestroyVolume)
   AWAIT_EXPECT_FALSE(authorizer.get()->authorize(request6));
 }
 
+
+// This tests the authorization of requests to set quotas.
+TYPED_TEST(AuthorizationTest, SetQuota)
+{
+  ACLs acls;
+
+  // "foo" principal can set quotas for all roles.
+  mesos::ACL::SetQuota* acl1 = acls.add_set_quotas();
+  acl1->mutable_principals()->add_values("foo");
+  acl1->mutable_roles()->set_type(mesos::ACL::Entity::ANY);
+
+  // "bar" principal can set quotas for "dev" role.
+  mesos::ACL::SetQuota* acl2 = acls.add_set_quotas();
+  acl2->mutable_principals()->add_values("bar");
+  acl2->mutable_roles()->add_values("dev");
+
+  // Anyone can set quotas for "test" role.
+  mesos::ACL::SetQuota* acl3 = acls.add_set_quotas();
+  acl3->mutable_principals()->set_type(mesos::ACL::Entity::ANY);
+  acl3->mutable_roles()->add_values("test");
+
+  // No other principal can set quotas.
+  mesos::ACL::SetQuota* acl4 = acls.add_set_quotas();
+  acl4->mutable_principals()->set_type(mesos::ACL::Entity::ANY);
+  acl4->mutable_roles()->set_type(mesos::ACL::Entity::NONE);
+
+  // Create an `Authorizer` with the ACLs.
+  Try<Authorizer*> create = TypeParam::create();
+  ASSERT_SOME(create);
+  Owned<Authorizer> authorizer(create.get());
+
+  Try<Nothing> initialized = authorizer.get()->initialize(acls);
+  ASSERT_SOME(initialized);
+
+  // Principal "foo" can set quota for all roles, so requests 1 and 2 will pass.
+  mesos::ACL::SetQuota request1;
+  request1.mutable_principals()->add_values("foo");
+  request1.mutable_roles()->set_type(mesos::ACL::Entity::ANY);
+  AWAIT_EXPECT_TRUE(authorizer.get()->authorize(request1));
+
+  mesos::ACL::SetQuota request2;
+  request2.mutable_principals()->add_values("foo");
+  request2.mutable_roles()->add_values("prod");
+  AWAIT_EXPECT_TRUE(authorizer.get()->authorize(request2));
+
+  // Principal "bar" can set quotas for role "dev", so this will pass.
+  mesos::ACL::SetQuota request3;
+  request3.mutable_principals()->add_values("bar");
+  request3.mutable_roles()->add_values("dev");
+  AWAIT_EXPECT_TRUE(authorizer.get()->authorize(request3));
+
+  // Principal "bar" can only set quotas for role "dev",
+  // so request 4 and 5 will fail.
+  mesos::ACL::SetQuota request4;
+  request4.mutable_principals()->add_values("bar");
+  request4.mutable_roles()->add_values("prod");
+  AWAIT_EXPECT_FALSE(authorizer.get()->authorize(request4));
+
+  mesos::ACL::SetQuota request5;
+  request5.mutable_principals()->add_values("bar");
+  request5.mutable_roles()->set_type(mesos::ACL::Entity::ANY);
+  AWAIT_EXPECT_FALSE(authorizer.get()->authorize(request5));
+
+  // Anyone can set quotas for role "test", so request 6 will pass.
+  mesos::ACL::SetQuota request6;
+  request6.mutable_principals()->set_type(mesos::ACL::Entity::ANY);
+  request6.mutable_roles()->add_values("test");
+  AWAIT_EXPECT_TRUE(authorizer.get()->authorize(request6));
+
+  // Principal "jeff" is not mentioned in the ACLs of the `Authorizer`, so it
+  // will be caught by the final ACL, which provides a default case that denies
+  // access for all other principals. This case will fail.
+  mesos::ACL::SetQuota request7;
+  request7.mutable_principals()->add_values("jeff");
+  request7.mutable_roles()->set_type(mesos::ACL::Entity::ANY);
+  AWAIT_EXPECT_FALSE(authorizer.get()->authorize(request7));
+}
+
 } // namespace tests {
 } // namespace internal {
 } // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/38a18c4d/src/tests/master_quota_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/master_quota_tests.cpp b/src/tests/master_quota_tests.cpp
index 0473869..ce2ecee 100644
--- a/src/tests/master_quota_tests.cpp
+++ b/src/tests/master_quota_tests.cpp
@@ -59,6 +59,7 @@ using process::http::BadRequest;
 using process::http::Conflict;
 using process::http::OK;
 using process::http::Response;
+using process::http::Unauthorized;
 
 using testing::_;
 using testing::DoAll;
@@ -74,7 +75,8 @@ namespace tests {
 //   * Request validation tests.
 //   * Sanity check tests.
 //   * Quota functionality tests.
-//   * Failover, and recovery tests.
+//   * Failover and recovery tests.
+//   * Authentication and authorization tests.
 
 // TODO(alexr): Once we have other allocators, convert this test into a
 // typed test over multiple allocators.
@@ -975,6 +977,194 @@ TEST_F(MasterQuotaTest, AvailableResourcesAfterRescinding)
 //   * Master fails simultaneously with multiple agents, rendering the cluster
 //     under quota (total quota sanity check).
 
+
+// These tests verify the authentication and authorization of quota requests.
+
+// Checks that a set quota request is rejected for unauthenticated principals.
+TEST_F(MasterQuotaTest, UnauthenticatedQuotaRequest)
+{
+  Try<PID<Master>> master = StartMaster();
+  ASSERT_SOME(master);
+
+  // We do not need an agent since a request should be rejected before
+  // we start looking at available resources.
+
+  // A request can contain any amount of resources because it will be rejected
+  // before we start looking at available resources.
+  Resources quotaResources = Resources::parse("cpus:1;mem:512", ROLE1).get();
+
+  // The master is configured so that only requests from `DEFAULT_CREDENTIAL`
+  // are authenticated.
+  Credential credential;
+  credential.set_principal("unknown-principal");
+  credential.set_secret("test-secret");
+
+  Future<Response> response1 = process::http::post(
+      master.get(),
+      "quota",
+      createBasicAuthHeaders(credential),
+      createRequestBody(quotaResources));
+
+  AWAIT_EXPECT_RESPONSE_STATUS_EQ(
+      Unauthorized("Mesos master").status, response1) << response1.get().body;
+
+  // The absense of credentials leads to authentication failure as well.
+  Future<Response> response2 = process::http::post(
+      master.get(),
+      "quota",
+      None(),
+      createRequestBody(quotaResources));
+
+  AWAIT_EXPECT_RESPONSE_STATUS_EQ(
+      Unauthorized("Mesos master").status, response2) << response2.get().body;
+
+  Shutdown();
+}
+
+
+// Checks that an authorized principal can set quota.
+TEST_F(MasterQuotaTest, AuthorizedQuotaSetRequest)
+{
+  TestAllocator<> allocator;
+  EXPECT_CALL(allocator, initialize(_, _, _, _));
+
+  // Setup ACLs so that the default principal can set quotas for `ROLE1`.
+  ACLs acls;
+  mesos::ACL::SetQuota* acl = acls.add_set_quotas();
+  acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL.principal());
+  acl->mutable_roles()->add_values(ROLE1);
+
+  master::Flags masterFlags = CreateMasterFlags();
+  masterFlags.acls = acls;
+
+  Try<PID<Master>> master = StartMaster(&allocator, masterFlags);
+  ASSERT_SOME(master);
+
+  // Start an agent and wait until it registers.
+  Future<Resources> agentTotalResources;
+  EXPECT_CALL(allocator, addSlave(_, _, _, _, _))
+    .WillOnce(DoAll(InvokeAddSlave(&allocator),
+                    FutureArg<3>(&agentTotalResources)));
+
+  Try<PID<Slave>> agent = StartSlave();
+  ASSERT_SOME(agent);
+
+  AWAIT_READY(agentTotalResources);
+  EXPECT_EQ(defaultAgentResources, agentTotalResources.get());
+
+  // Request quota for a portion of the resources available on the agent.
+  Resources quotaResources = Resources::parse("cpus:1;mem:512;", ROLE1).get();
+  EXPECT_TRUE(agentTotalResources.get().contains(quotaResources.flatten()));
+
+  Future<QuotaInfo> quotaInfo;
+  EXPECT_CALL(allocator, setQuota(Eq(ROLE1), _))
+    .WillOnce(DoAll(InvokeSetQuota(&allocator),
+                    FutureArg<1>(&quotaInfo)));
+
+  Future<Response> response = process::http::post(
+      master.get(),
+      "quota",
+      createBasicAuthHeaders(DEFAULT_CREDENTIAL),
+      createRequestBody(quotaResources));
+
+  AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response) << response.get().body;
+
+  AWAIT_READY(quotaInfo);
+
+  // TODO(nfnt): Quota removal authorization will add a principal field to
+  // `QuotaInfo`. Check it for the correct principal value.
+  EXPECT_EQ(ROLE1, quotaInfo.get().role());
+  EXPECT_EQ(quotaResources.flatten(), quotaInfo.get().guarantee());
+
+  Shutdown();
+}
+
+
+// Checks that set quota requests can be authorized without authentication
+// if an authorization rule exists that applies to anyone. The authorizer
+// will map the absence of a principal to "ANY".
+TEST_F(MasterQuotaTest, AuthorizedQuotaSetRequestWithoutPrincipal)
+{
+  TestAllocator<> allocator;
+  EXPECT_CALL(allocator, initialize(_, _, _, _));
+
+  // Setup ACLs so that the default principal can set quotas for `ROLE1`.
+  ACLs acls;
+  mesos::ACL::SetQuota* acl = acls.add_set_quotas();
+  acl->mutable_principals()->set_type(mesos::ACL::Entity::ANY);
+  acl->mutable_roles()->add_values(ROLE1);
+
+  // Disable authentication by not providing credentials.
+  master::Flags masterFlags = CreateMasterFlags();
+  masterFlags.acls = acls;
+  masterFlags.credentials = None();
+
+  Try<PID<Master>> master = StartMaster(&allocator, masterFlags);
+  ASSERT_SOME(master);
+
+  // Start an agent and wait until it registers.
+  Future<Resources> agentTotalResources;
+  EXPECT_CALL(allocator, addSlave(_, _, _, _, _))
+    .WillOnce(DoAll(InvokeAddSlave(&allocator),
+                    FutureArg<3>(&agentTotalResources)));
+
+  Try<PID<Slave>> agent = StartSlave();
+  ASSERT_SOME(agent);
+
+  AWAIT_READY(agentTotalResources);
+  EXPECT_EQ(defaultAgentResources, agentTotalResources.get());
+
+  // Request quota for a portion of the resources available on the agent.
+  Resources quotaResources = Resources::parse("cpus:1;mem:512;", ROLE1).get();
+  EXPECT_TRUE(agentTotalResources.get().contains(quotaResources.flatten()));
+
+  // Create a HTTP request without authorization headers.
+  Future<Response> response = process::http::post(
+      master.get(),
+      "quota",
+      None(),
+      createRequestBody(quotaResources));
+
+  AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response) << response.get().body;
+
+  Shutdown();
+}
+
+
+// Checks that an unauthorized principal cannot set quota.
+TEST_F(MasterQuotaTest, UnauthorizedQuotaSetRequest)
+{
+  // Setup ACLs so that no principal can set quotas for `ROLE1`.
+  ACLs acls;
+  mesos::ACL::SetQuota* acl = acls.add_set_quotas();
+  acl->mutable_principals()->set_type(mesos::ACL::Entity::NONE);
+  acl->mutable_roles()->add_values(ROLE1);
+
+  master::Flags masterFlags = CreateMasterFlags();
+  masterFlags.acls = acls;
+
+  Try<PID<Master>> master = StartMaster(masterFlags);
+  ASSERT_SOME(master);
+
+  // We do not need an agent since a request should be rejected before
+  // we start looking at available resources.
+
+  // A request can contain any amount of resources because it will be rejected
+  // before we start looking at available resources.
+  Resources quotaResources = Resources::parse("cpus:1;mem:512", ROLE1).get();
+
+  Future<Response> response = process::http::post(
+      master.get(),
+      "quota",
+      createBasicAuthHeaders(DEFAULT_CREDENTIAL),
+      createRequestBody(quotaResources));
+
+  AWAIT_EXPECT_RESPONSE_STATUS_EQ(
+      Unauthorized("Mesos master").status, response) << response.get().body;
+
+  Shutdown();
+}
+
 } // namespace tests {
 } // namespace internal {
 } // namespace mesos {


[4/7] mesos git commit: Quota: Documented quota authorization.

Posted by ti...@apache.org.
Quota: Documented quota authorization.

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


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

Branch: refs/heads/master
Commit: 50bf193f4853a96d1341ada5f167b5a98fe34336
Parents: 2940586
Author: Jan Schlicht <ja...@mesosphere.io>
Authored: Fri Dec 18 17:45:08 2015 +0100
Committer: Till Toenshoff <to...@me.com>
Committed: Fri Dec 18 17:49:43 2015 +0100

----------------------------------------------------------------------
 docs/authorization.md | 12 ++++++++----
 src/master/flags.cpp  |  6 ++++++
 2 files changed, 14 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/50bf193f/docs/authorization.md
----------------------------------------------------------------------
diff --git a/docs/authorization.md b/docs/authorization.md
index 1fc0e3f..0b108bf 100644
--- a/docs/authorization.md
+++ b/docs/authorization.md
@@ -4,16 +4,17 @@ layout: documentation
 
 # Authorization
 
-Mesos 0.20.0 adds support for framework authorization. Authorization allows
+Authorization currently allows
 
  1. Frameworks to (re-)register with authorized _roles_.
  2. Frameworks to launch tasks/executors as authorized _users_.
  3. Authorized _principals_ to shutdown frameworks through the "/teardown" HTTP endpoint.
+ 4. Authorized _principals_ to set quotas through the "/quota" HTTP endpoint.
 
 
 ## ACLs
 
-Authorization is implemented via Access Control Lists (ACLs). For each of the 3 cases described above, ACLs can be used to restrict access. Operators can setup ACLs in JSON format. See [mesos.proto](https://github.com/apache/mesos/blob/master/include/mesos/mesos.proto) for details.
+Authorization is implemented via Access Control Lists (ACLs). For each of the above cases, ACLs can be used to restrict access. Operators can setup ACLs in JSON format. See [mesos.proto](https://github.com/apache/mesos/blob/master/include/mesos/mesos.proto) for details.
 
 Each ACL specifies a set of `Subjects` that can perform an `Action` on a set of `Objects`.
 
@@ -22,16 +23,17 @@ The currently supported `Actions` are:
 1. "register_frameworks": Register frameworks
 2. "run_tasks": Run tasks/executors
 3. "shutdown_frameworks": Shutdown frameworks
+4. "set_quotas": Set quotas
 
 The currently supported `Subjects` are:
 
 1. "principals"
 	- Framework principals (used by "register_frameworks" and "run_tasks" actions)
-	- Usernames (used by "shutdown_frameworks" action)
+	- Usernames (used by "shutdown_frameworks" and "set_quotas" actions)
 
 The currently supported `Objects` are:
 
-1. "roles": Resource [roles](roles.md) that framework can register with (used by "register_frameworks" actions)
+1. "roles": Resource [roles](roles.md) that framework can register with (used by "register_frameworks" and "set_quotas" actions)
 2. "users": Unix user to launch the task/executor as (used by "run_tasks" actions)
 3. "framework_principals": Framework principals that can be shutdown by HTTP POST (used by "shutdown_frameworks" actions).
 
@@ -48,6 +50,8 @@ Similarly, when a framework launches a task, "run_tasks" ACLs are checked to see
 
 In the same vein, when a user/principal attempts to shutdown a framework using the "/teardown" HTTP endpoint on the master, "shutdown_frameworks" ACLs are checked to see if the principal is authorized to shutdown the given framework. If not authorized, the shutdown is rejected and the user receives an `Unauthorized` HTTP response.
 
+If no user/principal is provided in a request to an HTTP endpoint and authentication is disabled, the `ANY` subject is used in the authorization.
+
 There are couple of important things to note:
 
 1. ACLs are matched in the order that they are specified. In other words, the first matching ACL determines whether a request is authorized or not.

http://git-wip-us.apache.org/repos/asf/mesos/blob/50bf193f/src/master/flags.cpp
----------------------------------------------------------------------
diff --git a/src/master/flags.cpp b/src/master/flags.cpp
index f9223ed..a8861f3 100644
--- a/src/master/flags.cpp
+++ b/src/master/flags.cpp
@@ -259,6 +259,12 @@ mesos::internal::master::Flags::Flags()
       "      \"principals\": { \"values\": [\"a\", \"b\"] },\n"
       "      \"framework_principals\": { \"values\": [\"c\"] }\n"
       "    }\n"
+      "  ],\n"
+      "  \"set_quotas\": [\n"
+      "    {\n"
+      "      \"principals\": { \"values\": [\"a\"] },\n"
+      "      \"roles\": { \"values\": [\"a\", \"b\"] }\n"
+      "    }\n"
       "  ]\n"
       "}");
 


[7/7] mesos git commit: Quota: Updated comments in quota tests.

Posted by ti...@apache.org.
Quota: Updated comments in quota tests.

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


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

Branch: refs/heads/master
Commit: d13b65e1bcd6ccae459cacfec5ee7bd427a64979
Parents: 8e9469d
Author: Alexander Rukletsov <ru...@gmail.com>
Authored: Fri Dec 18 17:46:18 2015 +0100
Committer: Till Toenshoff <to...@me.com>
Committed: Fri Dec 18 20:52:30 2015 +0100

----------------------------------------------------------------------
 src/tests/master_quota_tests.cpp | 24 ++++++++++++------------
 1 file changed, 12 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/d13b65e1/src/tests/master_quota_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/master_quota_tests.cpp b/src/tests/master_quota_tests.cpp
index d855a0e..f134a8b 100644
--- a/src/tests/master_quota_tests.cpp
+++ b/src/tests/master_quota_tests.cpp
@@ -517,7 +517,7 @@ TEST_F(MasterQuotaTest, InsufficientResourcesSingleAgent)
   Try<PID<Master>> master = StartMaster(&allocator);
   ASSERT_SOME(master);
 
-  // Start an agent and wait until it registers.
+  // Start an agent and wait until its resources are available.
   Future<Resources> agentTotalResources;
   EXPECT_CALL(allocator, addSlave(_, _, _, _, _))
     .WillOnce(DoAll(InvokeAddSlave(&allocator),
@@ -612,7 +612,7 @@ TEST_F(MasterQuotaTest, InsufficientResourcesMultipleAgents)
   Try<PID<Master>> master = StartMaster(&allocator);
   ASSERT_SOME(master);
 
-  // Start one agent and wait until it registers.
+  // Start one agent and wait until its resources are available.
   Future<Resources> agent1TotalResources;
   EXPECT_CALL(allocator, addSlave(_, _, _, _, _))
     .WillOnce(DoAll(InvokeAddSlave(&allocator),
@@ -624,7 +624,7 @@ TEST_F(MasterQuotaTest, InsufficientResourcesMultipleAgents)
   AWAIT_READY(agent1TotalResources);
   EXPECT_EQ(defaultAgentResources, agent1TotalResources.get());
 
-  // Start another agent and wait until it registers.
+  // Start another agent and wait until its resources are available.
   Future<Resources> agent2TotalResources;
   EXPECT_CALL(allocator, addSlave(_, _, _, _, _))
     .WillOnce(DoAll(InvokeAddSlave(&allocator),
@@ -674,7 +674,7 @@ TEST_F(MasterQuotaTest, AvailableResourcesSingleAgent)
   Try<PID<Master>> master = StartMaster(&allocator);
   ASSERT_SOME(master);
 
-  // Start an agent and wait until it registers.
+  // Start an agent and wait until its resources are available.
   Future<Resources> agentTotalResources;
   EXPECT_CALL(allocator, addSlave(_, _, _, _, _))
     .WillOnce(DoAll(InvokeAddSlave(&allocator),
@@ -726,7 +726,7 @@ TEST_F(MasterQuotaTest, AvailableResourcesMultipleAgents)
   Try<PID<Master>> master = StartMaster(&allocator);
   ASSERT_SOME(master);
 
-  // Start one agent and wait until it registers.
+  // Start one agent and wait until its resources are available.
   Future<Resources> agent1TotalResources;
   EXPECT_CALL(allocator, addSlave(_, _, _, _, _))
     .WillOnce(DoAll(InvokeAddSlave(&allocator),
@@ -738,7 +738,7 @@ TEST_F(MasterQuotaTest, AvailableResourcesMultipleAgents)
   AWAIT_READY(agent1TotalResources);
   EXPECT_EQ(defaultAgentResources, agent1TotalResources.get());
 
-  // Start another agent and wait until it registers.
+  // Start another agent and wait until its resources are available.
   Future<Resources> agent2TotalResources;
   EXPECT_CALL(allocator, addSlave(_, _, _, _, _))
     .WillOnce(DoAll(InvokeAddSlave(&allocator),
@@ -799,7 +799,7 @@ TEST_F(MasterQuotaTest, AvailableResourcesAfterRescinding)
   Try<PID<Master>> master = StartMaster(&allocator);
   ASSERT_SOME(master);
 
-  // Start one agent and wait until it registers.
+  // Start one agent and wait until its resources are available.
   Future<Resources> agent1TotalResources;
   EXPECT_CALL(allocator, addSlave(_, _, _, _, _))
     .WillOnce(DoAll(InvokeAddSlave(&allocator),
@@ -811,7 +811,7 @@ TEST_F(MasterQuotaTest, AvailableResourcesAfterRescinding)
   AWAIT_READY(agent1TotalResources);
   EXPECT_EQ(defaultAgentResources, agent1TotalResources.get());
 
-  // Start another agent and wait until it registers.
+  // Start another agent and wait until its resources are available.
   Future<Resources> agent2TotalResources;
   EXPECT_CALL(allocator, addSlave(_, _, _, _, _))
     .WillOnce(DoAll(InvokeAddSlave(&allocator),
@@ -823,7 +823,7 @@ TEST_F(MasterQuotaTest, AvailableResourcesAfterRescinding)
   AWAIT_READY(agent2TotalResources);
   EXPECT_EQ(defaultAgentResources, agent2TotalResources.get());
 
-  // Start one more agent and wait until it registers.
+  // Start one more agent and wait until its resources are available.
   Future<Resources> agent3TotalResources;
   EXPECT_CALL(allocator, addSlave(_, _, _, _, _))
     .WillOnce(DoAll(InvokeAddSlave(&allocator),
@@ -999,7 +999,7 @@ TEST_F(MasterQuotaTest, NoAuthenticationNoAuthorization)
   Try<PID<Master>> master = StartMaster(&allocator, masterFlags);
   ASSERT_SOME(master);
 
-  // Start an agent and wait until it registers.
+  // Start an agent and wait until its resources are available.
   Future<Resources> agentTotalResources;
   EXPECT_CALL(allocator, addSlave(_, _, _, _, _))
     .WillOnce(DoAll(InvokeAddSlave(&allocator),
@@ -1118,7 +1118,7 @@ TEST_F(MasterQuotaTest, AuthorizedQuotaSetRequest)
   Try<PID<Master>> master = StartMaster(&allocator, masterFlags);
   ASSERT_SOME(master);
 
-  // Start an agent and wait until it registers.
+  // Start an agent and wait until its resources are available.
   Future<Resources> agentTotalResources;
   EXPECT_CALL(allocator, addSlave(_, _, _, _, _))
     .WillOnce(DoAll(InvokeAddSlave(&allocator),
@@ -1180,7 +1180,7 @@ TEST_F(MasterQuotaTest, AuthorizedQuotaSetRequestWithoutPrincipal)
   Try<PID<Master>> master = StartMaster(&allocator, masterFlags);
   ASSERT_SOME(master);
 
-  // Start an agent and wait until it registers.
+  // Start an agent and wait until its resources are available.
   Future<Resources> agentTotalResources;
   EXPECT_CALL(allocator, addSlave(_, _, _, _, _))
     .WillOnce(DoAll(InvokeAddSlave(&allocator),


[6/7] mesos git commit: Quota: Verified quota requests succeed when authn and authz are disabled.

Posted by ti...@apache.org.
Quota: Verified quota requests succeed when authn and authz are disabled.

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


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

Branch: refs/heads/master
Commit: 8e9469df884ad148bdc7070f7347f4fcef4dcd10
Parents: 38a18c4
Author: Alexander Rukletsov <ru...@gmail.com>
Authored: Fri Dec 18 17:46:07 2015 +0100
Committer: Till Toenshoff <to...@me.com>
Committed: Fri Dec 18 20:52:30 2015 +0100

----------------------------------------------------------------------
 src/tests/master_quota_tests.cpp | 78 +++++++++++++++++++++++++++++++++++
 1 file changed, 78 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/8e9469df/src/tests/master_quota_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/master_quota_tests.cpp b/src/tests/master_quota_tests.cpp
index ce2ecee..d855a0e 100644
--- a/src/tests/master_quota_tests.cpp
+++ b/src/tests/master_quota_tests.cpp
@@ -980,6 +980,84 @@ TEST_F(MasterQuotaTest, AvailableResourcesAfterRescinding)
 
 // These tests verify the authentication and authorization of quota requests.
 
+// Checks that quota set and remove requests succeed if both authentication
+// and authorization are disabled.
+TEST_F(MasterQuotaTest, NoAuthenticationNoAuthorization)
+{
+  TestAllocator<> allocator;
+  EXPECT_CALL(allocator, initialize(_, _, _, _));
+
+  // Disable authentication and authorization by providing neither
+  // credentials nor ACLs.
+  // TODO(alexr): Setting master `--acls` flag to `ACLs()` or `None()` seems
+  // to be semantically equal, however, the test harness currently does not
+  // allow `None()`. Once MESOS-4196 is resolved, use `None()` for clarity.
+  master::Flags masterFlags = CreateMasterFlags();
+  masterFlags.credentials = None();
+  masterFlags.acls = ACLs();
+
+  Try<PID<Master>> master = StartMaster(&allocator, masterFlags);
+  ASSERT_SOME(master);
+
+  // Start an agent and wait until it registers.
+  Future<Resources> agentTotalResources;
+  EXPECT_CALL(allocator, addSlave(_, _, _, _, _))
+    .WillOnce(DoAll(InvokeAddSlave(&allocator),
+                    FutureArg<3>(&agentTotalResources)));
+
+  Try<PID<Slave>> agent = StartSlave();
+  ASSERT_SOME(agent);
+
+  AWAIT_READY(agentTotalResources);
+  EXPECT_EQ(defaultAgentResources, agentTotalResources.get());
+
+  // Check whether quota can be set.
+  {
+    // Request quota for a portion of the resources available on the agent.
+    Resources quotaResources = Resources::parse("cpus:1;mem:512", ROLE1).get();
+    EXPECT_TRUE(agentTotalResources.get().contains(quotaResources.flatten()));
+
+    Future<QuotaInfo> receivedSetRequest;
+    EXPECT_CALL(allocator, setQuota(Eq(ROLE1), _))
+      .WillOnce(DoAll(InvokeSetQuota(&allocator),
+                      FutureArg<1>(&receivedSetRequest)));
+
+    // Send a set quota request with absent credentials.
+    Future<Response> response = process::http::post(
+        master.get(),
+        "quota",
+        None(),
+        createRequestBody(quotaResources));
+
+    // Quota request succeeds and reaches the allocator.
+    AWAIT_READY(receivedSetRequest);
+    AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response)
+      << response.get().body;
+  }
+
+  // Check whether quota can be removed.
+  {
+    Future<Nothing> receivedRemoveRequest;
+    EXPECT_CALL(allocator, removeQuota(Eq(ROLE1)))
+      .WillOnce(DoAll(InvokeRemoveQuota(&allocator),
+                      FutureSatisfy(&receivedRemoveRequest)));
+
+    // Send a remove quota request with absent credentials.
+    Future<Response> response = process::http::requestDelete(
+        master.get(),
+        "quota/" + ROLE1,
+        None());
+
+    // Quota request succeeds and reaches the allocator.
+    AWAIT_READY(receivedRemoveRequest);
+    AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response)
+      << response.get().body;
+  }
+
+  Shutdown();
+}
+
+
 // Checks that a set quota request is rejected for unauthenticated principals.
 TEST_F(MasterQuotaTest, UnauthenticatedQuotaRequest)
 {


[2/7] mesos git commit: Quota: Implemented authorization of set quota requests in the authorizer.

Posted by ti...@apache.org.
Quota: Implemented authorization of set quota requests in the authorizer.

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


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

Branch: refs/heads/master
Commit: df99dbb87cd93e392efb59d8d49c1376652e3bbb
Parents: 0f2bd3b
Author: Jan Schlicht <ja...@mesosphere.io>
Authored: Fri Dec 18 17:43:43 2015 +0100
Committer: Till Toenshoff <to...@me.com>
Committed: Fri Dec 18 17:49:19 2015 +0100

----------------------------------------------------------------------
 include/mesos/authorizer/authorizer.hpp | 19 ++++++++++++++++++
 src/authorizer/local/authorizer.cpp     | 30 ++++++++++++++++++++++++++++
 src/authorizer/local/authorizer.hpp     |  2 ++
 src/tests/mesos.cpp                     |  3 +++
 src/tests/mesos.hpp                     |  2 ++
 5 files changed, 56 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/df99dbb8/include/mesos/authorizer/authorizer.hpp
----------------------------------------------------------------------
diff --git a/include/mesos/authorizer/authorizer.hpp b/include/mesos/authorizer/authorizer.hpp
index aa9bb8b..19f6e1a 100644
--- a/include/mesos/authorizer/authorizer.hpp
+++ b/include/mesos/authorizer/authorizer.hpp
@@ -211,6 +211,25 @@ public:
   virtual process::Future<bool> authorize(
       const ACL::DestroyVolume& request) = 0;
 
+  /**
+   * Used to verify if a principal is allowed to set a quota for a specific
+   * role. The principal and role parameters are packed in `request`. If the
+   * principal is allowed to perform the action, this method returns true,
+   * otherwise it returns false. A third possible outcome is that the future
+   * fails. This indicates that the request could not be checked at the
+   * moment. This may be a temporary condition.
+   *
+   * @param request An instance of an `ACL::SetQuota` protobuf message. It
+   *     packs all the parameters needed to verify if the given principal is
+   *     allowed to request a quota with the specified role.
+   *
+   * @return true if the principal is allowed to set a quota for the specified
+   *     role or false otherwise. A failed future however indicates a problem
+   *     processing the request and the request can be retried.
+   */
+  virtual process::Future<bool> authorize(
+      const ACL::SetQuota& request) = 0;
+
 protected:
   Authorizer() {}
 };

http://git-wip-us.apache.org/repos/asf/mesos/blob/df99dbb8/src/authorizer/local/authorizer.cpp
----------------------------------------------------------------------
diff --git a/src/authorizer/local/authorizer.cpp b/src/authorizer/local/authorizer.cpp
index 46e181a..1d135fb 100644
--- a/src/authorizer/local/authorizer.cpp
+++ b/src/authorizer/local/authorizer.cpp
@@ -149,6 +149,21 @@ public:
     return acls.permissive(); // None of the ACLs match.
   }
 
+  Future<bool> authorize(const ACL::SetQuota& request)
+  {
+    foreach (const ACL::SetQuota& acl, acls.set_quotas()) {
+      // ACL matches if both subjects and objects match.
+      if (matches(request.principals(), acl.principals()) &&
+          matches(request.roles(), acl.roles())) {
+        // ACL is allowed if both subjects and objects are allowed.
+        return allows(request.principals(), acl.principals()) &&
+               allows(request.roles(), acl.roles());
+      }
+    }
+
+    return acls.permissive(); // None of the ACLs match.
+  }
+
 private:
   // Match matrix:
   //
@@ -283,6 +298,7 @@ LocalAuthorizer::~LocalAuthorizer()
   }
 }
 
+
 Try<Nothing> LocalAuthorizer::initialize(const Option<ACLs>& acls)
 {
   if (!acls.isSome()) {
@@ -408,5 +424,19 @@ Future<bool> LocalAuthorizer::authorize(const ACL::DestroyVolume& request)
       process, static_cast<F>(&LocalAuthorizerProcess::authorize), request);
 }
 
+
+Future<bool> LocalAuthorizer::authorize(const ACL::SetQuota& request)
+{
+  if (process == NULL) {
+    return Failure("Authorizer not initialized");
+  }
+
+  // Necessary to disambiguate.
+  typedef Future<bool>(LocalAuthorizerProcess::*F)(const ACL::SetQuota&);
+
+  return dispatch(
+      process, static_cast<F>(&LocalAuthorizerProcess::authorize), request);
+}
+
 } // namespace internal {
 } // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/df99dbb8/src/authorizer/local/authorizer.hpp
----------------------------------------------------------------------
diff --git a/src/authorizer/local/authorizer.hpp b/src/authorizer/local/authorizer.hpp
index eb95c9f..1563c11 100644
--- a/src/authorizer/local/authorizer.hpp
+++ b/src/authorizer/local/authorizer.hpp
@@ -65,6 +65,8 @@ public:
       const ACL::CreateVolume& request);
   virtual process::Future<bool> authorize(
       const ACL::DestroyVolume& request);
+  virtual process::Future<bool> authorize(
+      const ACL::SetQuota& request);
 
 private:
   LocalAuthorizer();

http://git-wip-us.apache.org/repos/asf/mesos/blob/df99dbb8/src/tests/mesos.cpp
----------------------------------------------------------------------
diff --git a/src/tests/mesos.cpp b/src/tests/mesos.cpp
index 50ba8c4..11ca051 100644
--- a/src/tests/mesos.cpp
+++ b/src/tests/mesos.cpp
@@ -696,6 +696,9 @@ MockAuthorizer::MockAuthorizer()
   EXPECT_CALL(*this, authorize(An<const mesos::ACL::DestroyVolume&>()))
     .WillRepeatedly(Return(true));
 
+  EXPECT_CALL(*this, authorize(An<const mesos::ACL::SetQuota&>()))
+    .WillRepeatedly(Return(true));
+
   EXPECT_CALL(*this, initialize(An<const Option<ACLs>&>()))
     .WillRepeatedly(Return(Nothing()));
 }

http://git-wip-us.apache.org/repos/asf/mesos/blob/df99dbb8/src/tests/mesos.hpp
----------------------------------------------------------------------
diff --git a/src/tests/mesos.hpp b/src/tests/mesos.hpp
index 1c6acab..582b018 100644
--- a/src/tests/mesos.hpp
+++ b/src/tests/mesos.hpp
@@ -1209,6 +1209,8 @@ public:
       authorize, process::Future<bool>(const ACL::CreateVolume& request));
   MOCK_METHOD1(
       authorize, process::Future<bool>(const ACL::DestroyVolume& request));
+  MOCK_METHOD1(
+      authorize, process::Future<bool>(const ACL::SetQuota& request));
 };
 
 


[3/7] mesos git commit: Quota: Implemented set quota request authorization.

Posted by ti...@apache.org.
Quota: Implemented set quota request authorization.

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


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

Branch: refs/heads/master
Commit: 2940586f64383668ecaf722c279f325d04fb3781
Parents: df99dbb
Author: Jan Schlicht <ja...@mesosphere.io>
Authored: Fri Dec 18 17:44:44 2015 +0100
Committer: Till Toenshoff <to...@me.com>
Committed: Fri Dec 18 17:49:34 2015 +0100

----------------------------------------------------------------------
 src/master/master.hpp        |  8 ++++++
 src/master/quota_handler.cpp | 51 ++++++++++++++++++++++++++++++++++++---
 2 files changed, 56 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/2940586f/src/master/master.hpp
----------------------------------------------------------------------
diff --git a/src/master/master.hpp b/src/master/master.hpp
index 7cb0e16..d493109 100644
--- a/src/master/master.hpp
+++ b/src/master/master.hpp
@@ -992,6 +992,14 @@ private:
     // (including rescinding) is moved to allocator.
     void rescindOffers(const mesos::quota::QuotaInfo& request) const;
 
+    process::Future<bool> authorize(
+        const Option<std::string>& principal,
+        const std::string& role) const;
+
+    process::Future<process::http::Response> _set(
+        const mesos::quota::QuotaInfo& quota,
+        bool forced) const;
+
     // To perform actions related to quota management, we require access to the
     // master data structures. No synchronization primitives are needed here
     // since `QuotaHandler`'s functions are invoked in the Master's actor.

http://git-wip-us.apache.org/repos/asf/mesos/blob/2940586f/src/master/quota_handler.cpp
----------------------------------------------------------------------
diff --git a/src/master/quota_handler.cpp b/src/master/quota_handler.cpp
index 1116787..27e12ab 100644
--- a/src/master/quota_handler.cpp
+++ b/src/master/quota_handler.cpp
@@ -263,8 +263,6 @@ Future<http::Response> Master::QuotaHandler::set(
     return Unauthorized("Mesos master", credential.error());
   }
 
-  // TODO(nfnt): Authorize the request.
-
   // Check that the request type is POST which is guaranteed by the master.
   CHECK_EQ("POST", request.method);
 
@@ -327,7 +325,28 @@ Future<http::Response> Master::QuotaHandler::set(
   const QuotaInfo& quotaInfo = create.get();
 
   // The force flag can be used to overwrite the capacityHeuristic check.
-  if (values.contains("force") && strings::lower(values["force"]) == "true") {
+  bool forced = values.contains("force") &&
+                strings::lower(values["force"]) == "true";
+
+  Option<string> principal =
+    credential.isSome() ? credential.get().principal() : Option<string>::none();
+
+  return authorize(principal, quotaInfo.role())
+    .then(defer(master->self(), [=](bool authorized) -> Future<http::Response> {
+      if (!authorized) {
+        return Unauthorized("Mesos master");
+      }
+
+      return _set(quotaInfo, forced);
+    }));
+}
+
+
+Future<http::Response> Master::QuotaHandler::_set(
+    const QuotaInfo& quotaInfo,
+    bool forced) const
+{
+  if (forced) {
     VLOG(1) << "Using force flag to override quota capacityHeuristic check";
   } else {
     // Validate whether a quota request can be satisfied.
@@ -438,6 +457,32 @@ Future<http::Response> Master::QuotaHandler::remove(
     }));
 }
 
+
+Future<bool> Master::QuotaHandler::authorize(
+    const Option<string>& principal,
+    const string& role) const
+{
+  if (master->authorizer.isNone()) {
+    return true;
+  }
+
+  LOG(INFO) << "Authorizing principal '"
+            << (principal.isSome() ? principal.get() : "ANY")
+            << "' to request quota for role '" << role << "'";
+
+  mesos::ACL::SetQuota request;
+
+  if (principal.isSome()) {
+    request.mutable_principals()->add_values(principal.get());
+  } else {
+    request.mutable_principals()->set_type(mesos::ACL::Entity::ANY);
+  }
+
+  request.mutable_roles()->add_values(role);
+
+  return master->authorizer.get()->authorize(request);
+}
+
 } // namespace master {
 } // namespace internal {
 } // namespace mesos {