Repository: mesos
Updated Branches:
  refs/heads/master 6b8682b9c -> b6b617a01


Added the VIEW_FLAGS authorization action.

Adds authorization for viewing flags. Instead of being part of
`get_endpoints` it uses its own action `VIEW_FLAGS` which is
used to restrict access to the `/flags` endpoint, as well as
to filter the flag related results of the `/state` endpoint on
both master and agents.

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


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

Branch: refs/heads/master
Commit: f35f606b08e0eaa4d452a81d439021433b242c14
Parents: 6b8682b
Author: Alexander Rojas <alexan...@mesosphere.io>
Authored: Thu Jun 30 13:19:57 2016 -0700
Committer: Vinod Kone <vinodk...@gmail.com>
Committed: Thu Jun 30 13:21:57 2016 -0700

----------------------------------------------------------------------
 include/mesos/authorizer/acls.proto       | 12 ++++
 include/mesos/authorizer/authorizer.proto |  4 ++
 src/authorizer/local/authorizer.cpp       | 22 +++++++
 src/common/http.cpp                       | 15 +++++
 src/common/http.hpp                       |  3 +
 src/master/http.cpp                       | 84 +++++++++++++++++-------
 src/slave/http.cpp                        | 77 ++++++++++++++++------
 src/tests/authorization_tests.cpp         | 56 ++++++++++++++++
 src/tests/master_authorization_tests.cpp  | 85 +++++++++++++++++++++++++
 src/tests/slave_authorization_tests.cpp   | 88 ++++++++++++++++++++++++++
 10 files changed, 405 insertions(+), 41 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/f35f606b/include/mesos/authorizer/acls.proto
----------------------------------------------------------------------
diff --git a/include/mesos/authorizer/acls.proto 
b/include/mesos/authorizer/acls.proto
index a6d93cd..31d5c14 100644
--- a/include/mesos/authorizer/acls.proto
+++ b/include/mesos/authorizer/acls.proto
@@ -244,6 +244,17 @@ message ACL {
     // access.
     required Entity logs = 2;
   }
+
+  // Which principals are authorized to access the command-line flags used to
+  // launch the master/agent.
+  message ViewFlags {
+    // Subjects: HTTP Username.
+    required Entity principals = 1;
+
+    // Objects: Given implicitly. Use Entity type ANY or NONE to allow or deny
+    // access.
+    required Entity flags = 2;
+  }
 }
 
 
@@ -294,4 +305,5 @@ message ACLs {
   repeated ACL.AccessSandbox access_sandboxes = 19;
   repeated ACL.AccessMesosLog access_mesos_logs = 20;
   repeated ACL.GetWeight get_weights = 21;
+  repeated ACL.ViewFlags view_flags = 22;
 }

http://git-wip-us.apache.org/repos/asf/mesos/blob/f35f606b/include/mesos/authorizer/authorizer.proto
----------------------------------------------------------------------
diff --git a/include/mesos/authorizer/authorizer.proto 
b/include/mesos/authorizer/authorizer.proto
index fc76796..e22d3a4 100644
--- a/include/mesos/authorizer/authorizer.proto
+++ b/include/mesos/authorizer/authorizer.proto
@@ -94,6 +94,10 @@ enum Action {
   // This action will not fill in any object fields, since the object
   // is the master/agent log itself.
   ACCESS_MESOS_LOG = 17;
+
+  // This action will not fill in any object fields, since the object
+  // is the entire set of flags.
+  VIEW_FLAGS = 18;
 }
 
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/f35f606b/src/authorizer/local/authorizer.cpp
----------------------------------------------------------------------
diff --git a/src/authorizer/local/authorizer.cpp 
b/src/authorizer/local/authorizer.cpp
index 3fade41..aadb7f6 100644
--- a/src/authorizer/local/authorizer.cpp
+++ b/src/authorizer/local/authorizer.cpp
@@ -257,6 +257,11 @@ public:
 
           break;
         }
+        case authorization::VIEW_FLAGS: {
+          aclObject.set_type(mesos::ACL::Entity::ANY);
+
+          break;
+        }
         case authorization::ACCESS_SANDBOX: {
           aclObject.set_type(mesos::ACL::Entity::ANY);
 
@@ -654,6 +659,17 @@ private:
 
         return acls_;
         break;
+      case authorization::VIEW_FLAGS:
+        foreach (const ACL::ViewFlags& acl, acls.view_flags()) {
+          GenericACL acl_;
+          acl_.subjects = acl.principals();
+          acl_.objects = acl.flags();
+
+          acls_.push_back(acl_);
+        }
+
+        return acls_;
+        break;
       case authorization::ACCESS_SANDBOX: {
         foreach (const ACL::AccessSandbox& acl, acls.access_sandboxes()) {
           GenericACL acl_;
@@ -762,6 +778,12 @@ Option<Error> LocalAuthorizer::validate(const ACLs& acls)
     }
   }
 
+  foreach (const ACL::ViewFlags& acl, acls.view_flags()) {
+    if (acl.flags().type() == ACL::Entity::SOME) {
+      return Error("acls.view_flags type must be either NONE or ANY");
+    }
+  }
+
   // TODO(alexr): Consider validating not only protobuf, but also the original
   // JSON in order to spot misspelled names. A misspelled action may affect
   // authorization result and hence lead to a security issue (e.g. when there

http://git-wip-us.apache.org/repos/asf/mesos/blob/f35f606b/src/common/http.cpp
----------------------------------------------------------------------
diff --git a/src/common/http.cpp b/src/common/http.cpp
index daf5672..2ef264f 100644
--- a/src/common/http.cpp
+++ b/src/common/http.cpp
@@ -698,4 +698,19 @@ bool approveViewTask(
   return approved.get();
 }
 
+
+bool approveViewFlags(
+    const Owned<ObjectApprover>& flagsApprover)
+{
+  ObjectApprover::Object object;
+
+  Try<bool> approved = flagsApprover->approved(object);
+  if (approved.isError()) {
+    LOG(WARNING) << "Error during Flags authorization: " << approved.error();
+    // TODO(joerg84): Consider exposing these errors to the caller.
+    return false;
+  }
+  return approved.get();
+}
+
 }  // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/f35f606b/src/common/http.hpp
----------------------------------------------------------------------
diff --git a/src/common/http.hpp b/src/common/http.hpp
index 55bd0ac..eb2d015 100644
--- a/src/common/http.hpp
+++ b/src/common/http.hpp
@@ -136,6 +136,9 @@ bool approveViewTask(
     const Task& task,
     const FrameworkInfo& frameworkInfo);
 
+
+bool approveViewFlags(const process::Owned<ObjectApprover>& flagsApprover);
+
 } // namespace mesos {
 
 #endif // __COMMON_HTTP_HPP__

http://git-wip-us.apache.org/repos/asf/mesos/blob/f35f606b/src/master/http.cpp
----------------------------------------------------------------------
diff --git a/src/master/http.cpp b/src/master/http.cpp
index 670b4e0..759e051 100644
--- a/src/master/http.cpp
+++ b/src/master/http.cpp
@@ -1455,7 +1455,11 @@ string Master::Http::FLAGS_HELP()
   return HELP(
     TLDR("Exposes the master's flag configuration."),
     None(),
-    AUTHENTICATION(true));
+    AUTHENTICATION(true),
+    AUTHORIZATION(
+        "Querying this endpoint requires that the current principal",
+        "is authorized to view all flags.",
+        "See the authorization documentation for details."));
 }
 
 
@@ -1469,7 +1473,27 @@ Future<Response> Master::Http::flags(
     return MethodNotAllowed({"GET"}, request.method);
   }
 
-  return OK(_flags(), request.url.query.get("jsonp"));
+  if (master->authorizer.isNone()) {
+    return OK(_flags(), request.url.query.get("jsonp"));
+  }
+
+  authorization::Request authRequest;
+  authRequest.set_action(authorization::VIEW_FLAGS);
+
+  if (principal.isSome()) {
+    authRequest.mutable_subject()->set_value(principal.get());
+  }
+
+  return master->authorizer.get()->authorized(authRequest)
+      .then(defer(
+          master->self(),
+          [this, request](bool authorized) -> Future<Response> {
+            if (authorized) {
+              return OK(_flags(), request.url.query.get("jsonp"));
+            } else {
+              return Forbidden();
+            }
+          }));
 }
 
 
@@ -2194,6 +2218,7 @@ Future<Response> Master::Http::state(
   Future<Owned<ObjectApprover>> frameworksApprover;
   Future<Owned<ObjectApprover>> tasksApprover;
   Future<Owned<ObjectApprover>> executorsApprover;
+  Future<Owned<ObjectApprover>> flagsApprover;
 
   if (master->authorizer.isSome()) {
     authorization::Subject subject;
@@ -2209,16 +2234,25 @@ Future<Response> Master::Http::state(
 
     executorsApprover = master->authorizer.get()->getObjectApprover(
         subject, authorization::VIEW_EXECUTOR);
+
+    flagsApprover = master->authorizer.get()->getObjectApprover(
+        subject, authorization::VIEW_FLAGS);
   } else {
     frameworksApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
     tasksApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
     executorsApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
+    flagsApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
   }
 
-  return collect(frameworksApprover, tasksApprover, executorsApprover)
+  return collect(
+      frameworksApprover,
+      tasksApprover,
+      executorsApprover,
+      flagsApprover)
     .then(defer(master->self(),
         [this, request](const tuple<Owned<ObjectApprover>,
                                     Owned<ObjectApprover>,
+                                    Owned<ObjectApprover>,
                                     Owned<ObjectApprover>>& approvers)
           -> Response {
       // This lambda is consumed before the outer lambda
@@ -2228,7 +2262,11 @@ Future<Response> Master::Http::state(
         Owned<ObjectApprover> frameworksApprover;
         Owned<ObjectApprover> tasksApprover;
         Owned<ObjectApprover> executorsApprover;
-        tie(frameworksApprover, tasksApprover, executorsApprover) = approvers;
+        Owned<ObjectApprover> flagsApprover;
+        tie(frameworksApprover,
+            tasksApprover,
+            executorsApprover,
+            flagsApprover) = approvers;
 
         writer->field("version", MESOS_VERSION);
 
@@ -2259,31 +2297,33 @@ Future<Response> Master::Http::state(
         writer->field("activated_slaves", master->_slaves_active());
         writer->field("deactivated_slaves", master->_slaves_inactive());
 
-        if (master->flags.cluster.isSome()) {
-          writer->field("cluster", master->flags.cluster.get());
-        }
-
         if (master->leader.isSome()) {
           writer->field("leader", master->leader.get().pid());
         }
 
-        if (master->flags.log_dir.isSome()) {
-          writer->field("log_dir", master->flags.log_dir.get());
-        }
+        if (approveViewFlags(flagsApprover)) {
+          if (master->flags.cluster.isSome()) {
+            writer->field("cluster", master->flags.cluster.get());
+          }
 
-        if (master->flags.external_log_file.isSome()) {
-          writer->field("external_log_file",
-             master->flags.external_log_file.get());
-        }
+          if (master->flags.log_dir.isSome()) {
+            writer->field("log_dir", master->flags.log_dir.get());
+          }
 
-        writer->field("flags", [this](JSON::ObjectWriter* writer) {
-          foreachvalue (const flags::Flag& flag, master->flags) {
-            Option<string> value = flag.stringify(master->flags);
-            if (value.isSome()) {
-              writer->field(flag.effective_name().value, value.get());
-            }
+          if (master->flags.external_log_file.isSome()) {
+            writer->field("external_log_file",
+                          master->flags.external_log_file.get());
           }
-        });
+
+          writer->field("flags", [this](JSON::ObjectWriter* writer) {
+              foreachvalue (const flags::Flag& flag, master->flags) {
+                Option<string> value = flag.stringify(master->flags);
+                if (value.isSome()) {
+                  writer->field(flag.effective_name().value, value.get());
+                }
+              }
+            });
+        }
 
         // Model all of the slaves.
         writer->field("slaves", [this](JSON::ArrayWriter* writer) {

http://git-wip-us.apache.org/repos/asf/mesos/blob/f35f606b/src/slave/http.cpp
----------------------------------------------------------------------
diff --git a/src/slave/http.cpp b/src/slave/http.cpp
index 335b773..60a780c 100644
--- a/src/slave/http.cpp
+++ b/src/slave/http.cpp
@@ -547,7 +547,10 @@ string Slave::Http::FLAGS_HELP()
   return HELP(
     TLDR("Exposes the agent's flag configuration."),
     None(),
-    AUTHENTICATION(true));
+    AUTHENTICATION(true),
+    AUTHORIZATION(
+        "The request principal should be authorized to view all flags.",
+        "See the authorization documentation for details."));
 }
 
 
@@ -561,7 +564,27 @@ Future<Response> Slave::Http::flags(
     return MethodNotAllowed({"GET"}, request.method);
   }
 
-  return OK(_flags(), request.url.query.get("jsonp"));
+  if (slave->authorizer.isNone()) {
+    return OK(_flags(), request.url.query.get("jsonp"));
+  }
+
+  authorization::Request authRequest;
+  authRequest.set_action(authorization::VIEW_FLAGS);
+
+  if (principal.isSome()) {
+    authRequest.mutable_subject()->set_value(principal.get());
+  }
+
+  return slave->authorizer.get()->authorized(authRequest)
+      .then(defer(
+          slave->self(),
+          [this, request](bool authorized) -> Future<Response> {
+            if (authorized) {
+              return OK(_flags(), request.url.query.get("jsonp"));
+            } else {
+              return Forbidden();
+            }
+          }));
 }
 
 
@@ -825,6 +848,7 @@ Future<Response> Slave::Http::state(
   Future<Owned<ObjectApprover>> frameworksApprover;
   Future<Owned<ObjectApprover>> tasksApprover;
   Future<Owned<ObjectApprover>> executorsApprover;
+  Future<Owned<ObjectApprover>> flagsApprover;
 
   if (slave->authorizer.isSome()) {
     authorization::Subject subject;
@@ -840,16 +864,25 @@ Future<Response> Slave::Http::state(
 
     executorsApprover = slave->authorizer.get()->getObjectApprover(
         subject, authorization::VIEW_EXECUTOR);
+
+    flagsApprover = slave->authorizer.get()->getObjectApprover(
+        subject, authorization::VIEW_FLAGS);
   } else {
     frameworksApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
     tasksApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
     executorsApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
+    flagsApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
   }
 
-  return collect(frameworksApprover, tasksApprover, executorsApprover)
+  return collect(
+      frameworksApprover,
+      tasksApprover,
+      executorsApprover,
+      flagsApprover)
     .then(defer(slave->self(),
         [this, request](const tuple<Owned<ObjectApprover>,
                         Owned<ObjectApprover>,
+                        Owned<ObjectApprover>,
                         Owned<ObjectApprover>>& approvers) -> Response {
       // This lambda is consumed before the outer lambda
       // returns, hence capture by reference is fine here.
@@ -858,7 +891,11 @@ Future<Response> Slave::Http::state(
         Owned<ObjectApprover> frameworksApprover;
         Owned<ObjectApprover> tasksApprover;
         Owned<ObjectApprover> executorsApprover;
-        tie(frameworksApprover, tasksApprover, executorsApprover) = approvers;
+        Owned<ObjectApprover> flagsApprover;
+        tie(frameworksApprover,
+            tasksApprover,
+            executorsApprover,
+            flagsApprover) = approvers;
 
         writer->field("version", MESOS_VERSION);
 
@@ -895,13 +932,24 @@ Future<Response> Slave::Http::state(
           }
         }
 
-        if (slave->flags.log_dir.isSome()) {
-          writer->field("log_dir", slave->flags.log_dir.get());
-        }
+        if (approveViewFlags(flagsApprover)) {
+          if (slave->flags.log_dir.isSome()) {
+            writer->field("log_dir", slave->flags.log_dir.get());
+          }
 
-        if (slave->flags.external_log_file.isSome()) {
-          writer->field(
-              "external_log_file", slave->flags.external_log_file.get());
+          if (slave->flags.external_log_file.isSome()) {
+            writer->field(
+                "external_log_file", slave->flags.external_log_file.get());
+          }
+
+          writer->field("flags", [this](JSON::ObjectWriter* writer) {
+            foreachvalue (const flags::Flag& flag, slave->flags) {
+              Option<string> value = flag.stringify(slave->flags);
+              if (value.isSome()) {
+                writer->field(flag.effective_name().value, value.get());
+              }
+            }
+          });
         }
 
         // Model all of the frameworks.
@@ -946,15 +994,6 @@ Future<Response> Slave::Http::state(
             writer->element(frameworkWriter);
           }
         });
-
-        writer->field("flags", [this](JSON::ObjectWriter* writer) {
-            foreachvalue (const flags::Flag& flag, slave->flags) {
-              Option<string> value = flag.stringify(slave->flags);
-              if (value.isSome()) {
-                writer->field(flag.effective_name().value, value.get());
-              }
-            }
-          });
       };
 
       return OK(jsonify(state), request.url.query.get("jsonp"));

http://git-wip-us.apache.org/repos/asf/mesos/blob/f35f606b/src/tests/authorization_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/authorization_tests.cpp 
b/src/tests/authorization_tests.cpp
index 9b99da1..c1e8ea6 100644
--- a/src/tests/authorization_tests.cpp
+++ b/src/tests/authorization_tests.cpp
@@ -2288,6 +2288,62 @@ TYPED_TEST(AuthorizationTest, OptionalObject)
   }
 }
 
+
+TYPED_TEST(AuthorizationTest, ViewFlags)
+{
+  // Setup ACLs.
+  ACLs acls;
+
+  {
+    // "foo" principal can see the flags.
+    mesos::ACL::ViewFlags* acl = acls.add_view_flags();
+    acl->mutable_principals()->add_values("foo");
+    acl->mutable_flags()->set_type(mesos::ACL::Entity::ANY);
+  }
+
+  {
+    // Nobody else can see the flags.
+    mesos::ACL::ViewFlags* acl = acls.add_view_flags();
+    acl->mutable_principals()->set_type(mesos::ACL::Entity::ANY);
+    acl->mutable_flags()->set_type(mesos::ACL::Entity::NONE);
+  }
+
+  // Create an `Authorizer` with the ACLs.
+  Try<Authorizer*> create = TypeParam::create(parameterize(acls));
+  ASSERT_SOME(create);
+  Owned<Authorizer> authorizer(create.get());
+
+
+  {
+    authorization::Request request;
+    request.set_action(authorization::VIEW_FLAGS);
+    request.mutable_subject()->set_value("foo");
+
+    AWAIT_EXPECT_TRUE(authorizer.get()->authorized(request));
+  }
+
+
+  {
+    authorization::Request request;
+    request.set_action(authorization::VIEW_FLAGS);
+    request.mutable_subject()->set_value("bar");
+
+    AWAIT_EXPECT_FALSE(authorizer.get()->authorized(request));
+  }
+
+  // Test that no authorzer is created with invalid flags.
+  {
+    ACLs invalid;
+
+    mesos::ACL::ViewFlags* acl = invalid.add_view_flags();
+    acl->mutable_principals()->add_values("foo");
+    acl->mutable_flags()->add_values("yoda");
+
+    Try<Authorizer*> create = TypeParam::create(parameterize(invalid));
+    EXPECT_ERROR(create);
+  }
+}
+
 } // namespace tests {
 } // namespace internal {
 } // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/f35f606b/src/tests/master_authorization_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/master_authorization_tests.cpp 
b/src/tests/master_authorization_tests.cpp
index 207dfb2..0807469 100644
--- a/src/tests/master_authorization_tests.cpp
+++ b/src/tests/master_authorization_tests.cpp
@@ -1691,6 +1691,91 @@ TYPED_TEST(MasterAuthorizerTest, FilterTasksEndpoint)
   driver.join();
 }
 
+
+TYPED_TEST(MasterAuthorizerTest, ViewFlags)
+{
+  ACLs acls;
+
+  {
+    // Default principal can see the flags.
+    mesos::ACL::ViewFlags* acl = acls.add_view_flags();
+    acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL.principal());
+    acl->mutable_flags()->set_type(ACL::Entity::ANY);
+  }
+
+  {
+    // Second default principal can not see the flags.
+    mesos::ACL::ViewFlags* acl = acls.add_view_flags();
+    acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL_2.principal());
+    acl->mutable_flags()->set_type(ACL::Entity::NONE);
+  }
+
+  // Create an `Authorizer` with the ACLs.
+  Try<Authorizer*> create = TypeParam::create(parameterize(acls));
+  ASSERT_SOME(create);
+  Owned<Authorizer> authorizer(create.get());
+
+  Try<Owned<cluster::Master>> master = this->StartMaster(authorizer.get());
+  ASSERT_SOME(master);
+
+  // The default principal should be able to access the flags.
+  {
+    Future<Response> response = http::get(
+        master.get()->pid,
+        "flags",
+        None(),
+        createBasicAuthHeaders(DEFAULT_CREDENTIAL));
+
+    AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response)
+        << response.get().body;
+
+    response = http::get(
+        master.get()->pid,
+        "state",
+        None(),
+        createBasicAuthHeaders(DEFAULT_CREDENTIAL));
+
+    AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response)
+        << response.get().body;
+
+    Try<JSON::Object> parse = JSON::parse<JSON::Object>(response.get().body);
+    ASSERT_SOME(parse);
+    JSON::Object state = parse.get();
+
+    ASSERT_TRUE(state.values["flags"].is<JSON::Object>());
+    EXPECT_TRUE(1u <= state.values["flags"].as<JSON::Object>().values.size());
+  }
+
+  // The second default principal should not have access to the
+  // /flags endpoint and get a filtered view of the /state one.
+  {
+    Future<Response> response = http::get(
+        master.get()->pid,
+        "flags",
+        None(),
+        createBasicAuthHeaders(DEFAULT_CREDENTIAL_2));
+
+    AWAIT_EXPECT_RESPONSE_STATUS_EQ(Forbidden().status, response)
+        << response.get().body;
+
+    response = http::get(
+        master.get()->pid,
+        "state",
+        None(),
+        createBasicAuthHeaders(DEFAULT_CREDENTIAL_2));
+
+    AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response)
+        << response.get().body;
+
+    Try<JSON::Object> parse = JSON::parse<JSON::Object>(response.get().body);
+    ASSERT_SOME(parse);
+    JSON::Object state = parse.get();
+
+    EXPECT_TRUE(state.values.find("flags") == state.values.end());
+  }
+}
+
+
 } // namespace tests {
 } // namespace internal {
 } // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/f35f606b/src/tests/slave_authorization_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/slave_authorization_tests.cpp 
b/src/tests/slave_authorization_tests.cpp
index 78221e2..f76ed3a 100644
--- a/src/tests/slave_authorization_tests.cpp
+++ b/src/tests/slave_authorization_tests.cpp
@@ -266,6 +266,94 @@ TYPED_TEST(SlaveAuthorizerTest, FilterStateEndpoint)
 }
 
 
+TYPED_TEST(SlaveAuthorizerTest, ViewFlags)
+{
+  ACLs acls;
+
+  {
+    // Default principal can see the flags.
+    mesos::ACL::ViewFlags* acl = acls.add_view_flags();
+    acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL.principal());
+    acl->mutable_flags()->set_type(ACL::Entity::ANY);
+  }
+
+  {
+    // Second default principal can not see the flags.
+    mesos::ACL::ViewFlags* acl = acls.add_view_flags();
+    acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL_2.principal());
+    acl->mutable_flags()->set_type(ACL::Entity::NONE);
+  }
+
+  // Create an `Authorizer` with the ACLs.
+  Try<Authorizer*> create = TypeParam::create(parameterize(acls));
+  ASSERT_SOME(create);
+  Owned<Authorizer> authorizer(create.get());
+
+  StandaloneMasterDetector detector;
+
+  Try<Owned<cluster::Slave>> agent =
+    this->StartSlave(&detector, authorizer.get());
+
+  ASSERT_SOME(agent);
+
+  // The default principal should be able to access the flags.
+  {
+    Future<Response> response = http::get(
+        agent.get()->pid,
+        "flags",
+        None(),
+        createBasicAuthHeaders(DEFAULT_CREDENTIAL));
+
+    AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response)
+        << response.get().body;
+
+    response = http::get(
+        agent.get()->pid,
+        "state",
+        None(),
+        createBasicAuthHeaders(DEFAULT_CREDENTIAL));
+
+    AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response)
+        << response.get().body;
+
+    Try<JSON::Object> parse = JSON::parse<JSON::Object>(response.get().body);
+    ASSERT_SOME(parse);
+    JSON::Object state = parse.get();
+
+    ASSERT_TRUE(state.values["flags"].is<JSON::Object>());
+    EXPECT_TRUE(1u <= state.values["flags"].as<JSON::Object>().values.size());
+  }
+
+  // The second default principal should not have access to the
+  // /flags endpoint and get a filtered view of the /state one.
+  {
+    Future<Response> response = http::get(
+        agent.get()->pid,
+        "flags",
+        None(),
+        createBasicAuthHeaders(DEFAULT_CREDENTIAL_2));
+
+    AWAIT_EXPECT_RESPONSE_STATUS_EQ(Forbidden().status, response)
+        << response.get().body;
+
+    response = http::get(
+        agent.get()->pid,
+        "state",
+        None(),
+        createBasicAuthHeaders(DEFAULT_CREDENTIAL_2));
+
+    AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response)
+        << response.get().body;
+
+    Try<JSON::Object> parse = JSON::parse<JSON::Object>(response.get().body);
+    ASSERT_SOME(parse);
+    JSON::Object state = parse.get();
+
+    EXPECT_TRUE(state.values.find("flags") == state.values.end());
+  }
+}
+
+
 // Parameterized fixture for agent-specific authorization tests. The
 // path of the tested endpoint is passed as the only parameter.
 class SlaveEndpointTest:

Reply via email to