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(),