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 <benjamin.bann...@mesosphere.io> Authored: Thu Aug 25 20:02:06 2016 -0700 Committer: Jie Yu <yujie....@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(),