You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by ji...@apache.org on 2016/08/26 04:28:35 UTC

mesos git commit: Added basic tests for capabilities API.

Repository: mesos
Updated Branches:
  refs/heads/master 79b6a9e38 -> 9d0976f73


Added basic tests for capabilities API.

This test is based on the work in:
https://reviews.apache.org/r/46371/

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


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

Branch: refs/heads/master
Commit: 9d0976f730a1fb177876145c96d2b26e2c787437
Parents: 79b6a9e
Author: Benjamin Bannier <be...@mesosphere.io>
Authored: Thu Aug 25 20:02:06 2016 -0700
Committer: Jie Yu <yu...@gmail.com>
Committed: Thu Aug 25 21:28:26 2016 -0700

----------------------------------------------------------------------
 src/Makefile.am                                 |   4 +
 src/linux/capabilities.cpp                      |  46 +++++-
 src/linux/capabilities.hpp                      |   5 +-
 src/tests/CMakeLists.txt                        |   1 +
 .../containerizer/capabilities_test_helper.cpp  | 151 ++++++++++++++++++
 .../containerizer/capabilities_test_helper.hpp  |  57 +++++++
 src/tests/containerizer/capabilities_tests.cpp  | 155 +++++++++++++++++++
 src/tests/test_helper_main.cpp                  |   3 +
 8 files changed, 419 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/9d0976f7/src/Makefile.am
----------------------------------------------------------------------
diff --git a/src/Makefile.am b/src/Makefile.am
index 120a715..b577b42 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -993,6 +993,7 @@ libmesos_no_3rdparty_la_SOURCES +=					\
   tests/utils.hpp							\
   tests/zookeeper.hpp							\
   tests/zookeeper_test_server.hpp					\
+  tests/containerizer/capabilities_test_helper.hpp			\
   tests/containerizer/docker_archive.hpp				\
   tests/containerizer/isolator.hpp					\
   tests/containerizer/launcher.hpp					\
@@ -1915,6 +1916,7 @@ test_helper_SOURCES =						\
   tests/containerizer/memory_test_helper.cpp
 if OS_LINUX
 test_helper_SOURCES +=						\
+  tests/containerizer/capabilities_test_helper.cpp		\
   tests/containerizer/setns_test_helper.cpp
 endif
 test_helper_CPPFLAGS = $(mesos_tests_CPPFLAGS)
@@ -2175,6 +2177,8 @@ mesos_tests_DEPENDENCIES =					\
 if OS_LINUX
 mesos_tests_SOURCES +=						\
   tests/ldcache_tests.cpp					\
+  tests/containerizer/capabilities_tests.cpp			\
+  tests/containerizer/capabilities_test_helper.cpp		\
   tests/containerizer/cgroups_isolator_tests.cpp		\
   tests/containerizer/cgroups_tests.cpp				\
   tests/containerizer/cni_isolator_tests.cpp			\

http://git-wip-us.apache.org/repos/asf/mesos/blob/9d0976f7/src/linux/capabilities.cpp
----------------------------------------------------------------------
diff --git a/src/linux/capabilities.cpp b/src/linux/capabilities.cpp
index 67ade40..e090fc5 100644
--- a/src/linux/capabilities.cpp
+++ b/src/linux/capabilities.cpp
@@ -137,8 +137,8 @@ Set<Capability> ProcessCapabilities::get(const Type& type) const
 {
   switch (type) {
     case EFFECTIVE:   return effective;
-    case INHERITABLE: return inheritable;
     case PERMITTED:   return permitted;
+    case INHERITABLE: return inheritable;
     case BOUNDING:    return bounding;
   }
 
@@ -161,6 +161,36 @@ void ProcessCapabilities::set(
 }
 
 
+void ProcessCapabilities::add(
+    const Type& type,
+    const Capability& capability)
+{
+  switch (type) {
+    case EFFECTIVE:   effective.insert(capability);   return;
+    case PERMITTED:   permitted.insert(capability);   return;
+    case INHERITABLE: inheritable.insert(capability); return;
+    case BOUNDING:    bounding.insert(capability);    return;
+  }
+
+  UNREACHABLE();
+}
+
+
+void ProcessCapabilities::drop(
+    const Type& type,
+    const Capability& capability)
+{
+  switch (type) {
+    case EFFECTIVE:   effective.erase(capability);   return;
+    case PERMITTED:   permitted.erase(capability);   return;
+    case INHERITABLE: inheritable.erase(capability); return;
+    case BOUNDING:    bounding.erase(capability);    return;
+  }
+
+  UNREACHABLE();
+}
+
+
 Try<Capabilities> Capabilities::create()
 {
   // Check for compatible linux capability version.
@@ -312,6 +342,18 @@ Capability convert(const CapabilityInfo::Capability& capability)
 }
 
 
+Set<Capability> convert(const CapabilityInfo& capabilityInfo)
+{
+  Set<Capability> result;
+
+  foreach (int value, capabilityInfo.capabilities()) {
+    result.insert(convert(static_cast<CapabilityInfo::Capability>(value)));
+  }
+
+  return result;
+}
+
+
 CapabilityInfo convert(const Set<Capability>& capabilities)
 {
   CapabilityInfo capabilityInfo;
@@ -377,8 +419,8 @@ ostream& operator<<(ostream& stream, const Type& type)
 {
   switch (type) {
     case EFFECTIVE:   return stream << "eff";
-    case INHERITABLE: return stream << "inh";
     case PERMITTED:   return stream << "perm";
+    case INHERITABLE: return stream << "inh";
     case BOUNDING:    return stream << "bnd";
   }
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/9d0976f7/src/linux/capabilities.hpp
----------------------------------------------------------------------
diff --git a/src/linux/capabilities.hpp b/src/linux/capabilities.hpp
index 177cb98..a89f639 100644
--- a/src/linux/capabilities.hpp
+++ b/src/linux/capabilities.hpp
@@ -95,6 +95,8 @@ class ProcessCapabilities
 public:
   Set<Capability> get(const Type& type) const;
   void set(const Type& type, const Set<Capability>& capabilities);
+  void add(const Type& type, const Capability& capability);
+  void drop(const Type& type, const Capability& capability);
 
 private:
   friend std::ostream& operator<<(
@@ -173,7 +175,8 @@ private:
 };
 
 
-Capability convert(const CapabilityInfo::Capability& capabilityInfo);
+Capability convert(const CapabilityInfo::Capability& capability);
+Set<Capability> convert(const CapabilityInfo& capabilityInfo);
 CapabilityInfo convert(const Set<Capability>& capabilitySet);
 
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/9d0976f7/src/tests/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index a380fed..f5d66dc 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -29,6 +29,7 @@ set(TEST_HELPER_SRC
 if (LINUX)
   set(TEST_HELPER_SRC
     ${TEST_HELPER_SRC}
+    containerizer/capabilities_test_helper.cpp
     containerizer/setns_test_helper.cpp
     )
 endif (LINUX)

http://git-wip-us.apache.org/repos/asf/mesos/blob/9d0976f7/src/tests/containerizer/capabilities_test_helper.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/capabilities_test_helper.cpp b/src/tests/containerizer/capabilities_test_helper.cpp
new file mode 100644
index 0000000..f982dbd
--- /dev/null
+++ b/src/tests/containerizer/capabilities_test_helper.cpp
@@ -0,0 +1,151 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <ostream>
+#include <string>
+#include <vector>
+
+#include <stout/os/su.hpp>
+
+#include <stout/os/raw/argv.hpp>
+
+#include <mesos/type_utils.hpp>
+
+#include "common/parse.hpp"
+
+#include "linux/capabilities.hpp"
+
+#include "tests/containerizer/capabilities_test_helper.hpp"
+
+namespace capabilities = mesos::internal::capabilities;
+
+using std::cerr;
+using std::endl;
+using std::string;
+using std::vector;
+
+using mesos::internal::capabilities::Capabilities;
+using mesos::internal::capabilities::Capability;
+using mesos::internal::capabilities::ProcessCapabilities;
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+const char CapabilitiesTestHelper::NAME[] = "Capabilities";
+
+
+CapabilitiesTestHelper::Flags::Flags()
+{
+  add(&user,
+      "user",
+      "User to be used for the test.");
+
+  add(&capabilities,
+      "capabilities",
+      "Capabilities to be set for the process.");
+}
+
+
+int CapabilitiesTestHelper::execute()
+{
+  Try<Capabilities> manager = Capabilities::create();
+  if (manager.isError()) {
+    cerr << "Failed to initialize capabilities manager: "
+         << manager.error() << endl;
+
+    return EXIT_FAILURE;
+  }
+
+  if (flags.capabilities.isNone()) {
+    cerr << "Missing '--capabilities'" << endl;
+    return EXIT_FAILURE;
+  }
+
+  if (flags.user.isSome()) {
+    Try<Nothing> keepCaps = manager->keepCapabilitiesOnSetUid();
+    if (keepCaps.isError()) {
+      cerr << "Failed to set PR_SET_KEEPCAPS on the process: "
+           << keepCaps.error() << endl;
+
+      return EXIT_FAILURE;
+    }
+
+    Try<Nothing> su = os::su(flags.user.get());
+    if (su.isError()) {
+      cerr << "Failed to change user to '" << flags.user.get() << "'"
+           << ": " << su.error() << endl;
+
+      return EXIT_FAILURE;
+    }
+
+    // TODO(jieyu): Consider to clear PR_SET_KEEPCAPS.
+  }
+
+  Try<ProcessCapabilities> capabilities = manager->get();
+  if (capabilities.isError()) {
+    cerr << "Failed to get capabilities for the current process: "
+         << capabilities.error() << endl;
+
+    return EXIT_FAILURE;
+  }
+
+  // After 'os::su', 'effective' set is cleared. Since `SETPCAP` is
+  // required in the `effective` set of a process to change the
+  // bounding set, we need to restore it first.
+  if (flags.user.isSome()) {
+    capabilities->add(capabilities::EFFECTIVE, capabilities::SETPCAP);
+
+    Try<Nothing> set = manager->set(capabilities.get());
+    if (set.isError()) {
+      cerr << "Failed to add SETPCAP to the effective set: "
+           << set.error() << endl;
+
+      return EXIT_FAILURE;
+    }
+  }
+
+  Set<Capability> target = capabilities::convert(flags.capabilities.get());
+
+  capabilities->set(capabilities::EFFECTIVE,   target);
+  capabilities->set(capabilities::PERMITTED,   target);
+  capabilities->set(capabilities::INHERITABLE, target);
+  capabilities->set(capabilities::BOUNDING,    target);
+
+  Try<Nothing> set = manager->set(capabilities.get());
+  if (set.isError()) {
+    cerr << "Failed to set process capabilities: " << set.error() << endl;
+    return EXIT_FAILURE;
+  }
+
+  vector<string> argv = {"ping", "-c", "1", "localhost"};
+
+  // We use `ping` as a command since it has setuid bit set. This
+  // allows us to test if capabilities whitelist works or not.
+  ::execvp("ping", os::raw::Argv(argv));
+
+  cerr << "'ping' failed: " << strerror(errno) << endl;
+
+  return errno;
+}
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/9d0976f7/src/tests/containerizer/capabilities_test_helper.hpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/capabilities_test_helper.hpp b/src/tests/containerizer/capabilities_test_helper.hpp
new file mode 100644
index 0000000..256ee3b
--- /dev/null
+++ b/src/tests/containerizer/capabilities_test_helper.hpp
@@ -0,0 +1,57 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef __CAPABILITIES_TEST_HELPER_HPP__
+#define __CAPABILITIES_TEST_HELPER_HPP__
+
+#include <string>
+
+#include <stout/option.hpp>
+#include <stout/subcommand.hpp>
+
+#include <mesos/mesos.hpp>
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+class CapabilitiesTestHelper : public Subcommand
+{
+public:
+  static const char NAME[];
+
+  struct Flags : public flags::FlagsBase
+  {
+    Flags();
+
+    Option<std::string> user;
+    Option<CapabilityInfo> capabilities;
+  };
+
+  CapabilitiesTestHelper() : Subcommand(NAME) {}
+
+  Flags flags;
+
+protected:
+  virtual int execute();
+  virtual flags::FlagsBase* getFlags() { return &flags; }
+};
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {
+
+#endif // __CAPABILITIES_TEST_HELPER_HPP__

http://git-wip-us.apache.org/repos/asf/mesos/blob/9d0976f7/src/tests/containerizer/capabilities_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/capabilities_tests.cpp b/src/tests/containerizer/capabilities_tests.cpp
new file mode 100644
index 0000000..ec75698
--- /dev/null
+++ b/src/tests/containerizer/capabilities_tests.cpp
@@ -0,0 +1,155 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <errno.h>
+
+#include <string>
+#include <vector>
+
+#include <process/gtest.hpp>
+#include <process/subprocess.hpp>
+
+#include <stout/gtest.hpp>
+#include <stout/os.hpp>
+
+#include "linux/capabilities.hpp"
+
+#include "tests/mesos.hpp"
+#include "tests/utils.hpp"
+
+#include "tests/containerizer/capabilities_test_helper.hpp"
+
+using std::string;
+using std::vector;
+
+using process::Future;
+using process::NO_SETSID;
+using process::Subprocess;
+
+using mesos::internal::capabilities::Capabilities;
+using mesos::internal::capabilities::Capability;
+using mesos::internal::capabilities::ProcessCapabilities;
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+constexpr char CAPS_TEST_UNPRIVILEGED_USER[] = "nobody";
+
+
+class CapabilitiesTest : public ::testing::Test
+{
+public:
+  // Launch 'ping' using the given capabilities and user.
+  Try<Subprocess> ping(
+      const Set<Capability>& capabilities,
+      const Option<string>& user = None())
+  {
+    CapabilitiesTestHelper helper;
+
+    helper.flags.user = user;
+    helper.flags.capabilities = capabilities::convert(capabilities);
+
+    vector<string> argv = {
+      "test-helper",
+      CapabilitiesTestHelper::NAME
+    };
+
+    return subprocess(
+        getTestHelperPath("test-helper"),
+        argv,
+        Subprocess::FD(STDIN_FILENO),
+        Subprocess::FD(STDOUT_FILENO),
+        Subprocess::FD(STDERR_FILENO),
+        NO_SETSID,
+        &helper.flags);
+  }
+};
+
+
+// This test verifies that an operation ('ping') that needs `NET_RAW`
+// capability does not succeed if the capability `NET_RAW` is dropped.
+TEST_F(CapabilitiesTest, ROOT_PingWithNoNetRawCaps)
+{
+  Try<Capabilities> manager = Capabilities::create();
+  ASSERT_SOME(manager);
+
+  Try<ProcessCapabilities> capabilities = manager->get();
+  ASSERT_SOME(capabilities);
+
+  capabilities->drop(capabilities::PERMITTED, capabilities::NET_RAW);
+
+  Try<Subprocess> s = ping(capabilities->get(capabilities::PERMITTED));
+  ASSERT_SOME(s);
+
+  Future<Option<int>> status = s->status();
+  AWAIT_READY(status);
+
+  ASSERT_SOME(status.get());
+  EXPECT_TRUE(WIFEXITED(status->get()));
+  EXPECT_NE(0, WEXITSTATUS(status->get()));
+}
+
+
+// This test verifies that the effective capabilities of a process can
+// be controlled after `setuid` system call. An operation ('ping')
+// that needs `NET_RAW` capability does not succeed if the capability
+// `NET_RAW` is dropped.
+TEST_F(CapabilitiesTest, ROOT_PingWithNoNetRawCapsChangeUser)
+{
+  Try<Capabilities> manager = Capabilities::create();
+  ASSERT_SOME(manager);
+
+  Try<ProcessCapabilities> capabilities = manager->get();
+  ASSERT_SOME(capabilities);
+
+  capabilities->drop(capabilities::PERMITTED, capabilities::NET_RAW);
+
+  Try<Subprocess> s = ping(
+      capabilities->get(capabilities::PERMITTED),
+      CAPS_TEST_UNPRIVILEGED_USER);
+
+  ASSERT_SOME(s);
+
+  Future<Option<int>> status = s->status();
+  AWAIT_READY(status);
+
+  ASSERT_SOME(status.get());
+  EXPECT_TRUE(WIFEXITED(status->get()));
+  EXPECT_NE(0, WEXITSTATUS(status->get()));
+}
+
+
+// This Test verifies that 'ping' would work with just the minimum
+// capability it requires ('NET_RAW').
+TEST_F(CapabilitiesTest, ROOT_PingWithJustNetRawCap)
+{
+  Set<Capability> capabilities = {capabilities::NET_RAW};
+
+  Try<Subprocess> s = ping(capabilities, CAPS_TEST_UNPRIVILEGED_USER);
+  ASSERT_SOME(s);
+
+  Future<Option<int>> status = s->status();
+  AWAIT_READY(status);
+
+  ASSERT_SOME(status.get());
+  EXPECT_TRUE(WIFEXITED(status->get()));
+  EXPECT_EQ(0, WEXITSTATUS(status->get()));
+}
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/9d0976f7/src/tests/test_helper_main.cpp
----------------------------------------------------------------------
diff --git a/src/tests/test_helper_main.cpp b/src/tests/test_helper_main.cpp
index 06aacc3..129a5e4 100644
--- a/src/tests/test_helper_main.cpp
+++ b/src/tests/test_helper_main.cpp
@@ -21,12 +21,14 @@
 
 #include "tests/containerizer/memory_test_helper.hpp"
 #ifdef __linux__
+#include "tests/containerizer/capabilities_test_helper.hpp"
 #include "tests/containerizer/setns_test_helper.hpp"
 #endif
 
 using mesos::internal::tests::ActiveUserTestHelper;
 using mesos::internal::tests::MemoryTestHelper;
 #ifdef __linux__
+using mesos::internal::tests::CapabilitiesTestHelper;
 using mesos::internal::tests::SetnsTestHelper;
 #endif
 
@@ -38,6 +40,7 @@ int main(int argc, char** argv)
       argc,
       argv,
 #ifdef __linux__
+      new CapabilitiesTestHelper(),
       new SetnsTestHelper(),
 #endif
       new ActiveUserTestHelper(),