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:02:03 UTC

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

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 {