Future<Nothing> NetworkCniIsolatorProcess::detach( const ContainerID& containerId, const std::string& networkName) { CHECK(infos.contains(containerId)); CHECK(infos[containerId]->containerNetworks.contains(networkName)); const ContainerNetwork& containerNetwork = infos[containerId]->containerNetworks[networkName]; // Prepare environment variables for CNI plugin. map<string, string> environment; environment["CNI_COMMAND"] = "DEL"; environment["CNI_CONTAINERID"] = containerId.value(); environment["CNI_PATH"] = pluginDir.get(); environment["CNI_IFNAME"] = containerNetwork.ifName; environment["CNI_NETNS"] = paths::getNamespacePath(rootDir.get(), containerId.value()); // Some CNI plugins need to run "iptables" to set up IP Masquerade, so we // need to set the "PATH" environment variable so that the plugin can locate // the "iptables" executable file. Option<string> value = os::getenv("PATH"); if (value.isSome()) { environment["PATH"] = value.get(); } else { environment["PATH"] = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"; } const NetworkConfigInfo& networkConfig = networkConfigs[networkName]; // Invoke the CNI plugin. const string& plugin = networkConfig.config.type(); Try<Subprocess> s = subprocess( path::join(pluginDir.get(), plugin), {plugin}, Subprocess::PATH(networkConfig.path), Subprocess::PIPE(), Subprocess::PATH("/dev/null"), NO_SETSID, None(), environment); if (s.isError()) { return Failure( "Failed to execute the CNI plugin '" + plugin + "': " + s.error()); } return await(s->status(), io::read(s->out().get())) .then(defer( PID<NetworkCniIsolatorProcess>(this), &NetworkCniIsolatorProcess::_detach, containerId, networkName, plugin, lambda::_1)); }
// This Test verifies that 'ping' would work with just the minimum // capability it requires ('NET_RAW' and potentially 'NET_ADMIN'). // // NOTE: Some Linux distributions install `ping` with `NET_RAW` and // `NET_ADMIN` in both the effective and permitted set in the file // capabilities. We only require `NET_RAW` for our tests, while // `NET_RAW` is needed for setting packet marks // (https://bugzilla.redhat.com/show_bug.cgi?id=802197). In such // distributions, setting 'NET_ADMIN' is required to bypass the // 'capability-dumb' check by the kernel. A 'capability-dump' // application is a traditional set-user-ID-root program that has been // switched to use file capabilities, but whose code has not been // modified to understand capabilities. For such applications, the // kernel checks if the process obtained all permitted capabilities // that were specified in the file permitted set during 'exec'. TEST_F(CapabilitiesTest, ROOT_PingWithJustNetRawSysAdminCap) { Set<Capability> capabilities = { capabilities::NET_RAW, capabilities::NET_ADMIN }; 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())); }
// 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())); }
Future<int> CheckerProcess::commandCheck( const check::Command& cmd, const runtime::Plain& plain) { const CommandInfo& command = cmd.info; map<string, string> environment = os::environment(); foreach (const Environment::Variable& variable, command.environment().variables()) { environment[variable.name()] = variable.value(); } // Launch the subprocess. Try<Subprocess> s = Error("Not launched"); if (command.shell()) { // Use the shell variant. VLOG(1) << "Launching " << name << " '" << command.value() << "'" << " for task '" << taskId << "'"; s = process::subprocess( command.value(), Subprocess::PATH(os::DEV_NULL), Subprocess::FD(STDERR_FILENO), Subprocess::FD(STDERR_FILENO), environment, getCustomCloneFunc(plain)); } else { // Use the exec variant. vector<string> argv( std::begin(command.arguments()), std::end(command.arguments())); VLOG(1) << "Launching " << name << " [" << command.value() << ", " << strings::join(", ", argv) << "] for task '" << taskId << "'"; s = process::subprocess( command.value(), argv, Subprocess::PATH(os::DEV_NULL), Subprocess::FD(STDERR_FILENO), Subprocess::FD(STDERR_FILENO), nullptr, environment, getCustomCloneFunc(plain)); } if (s.isError()) { return Failure("Failed to create subprocess: " + s.error()); } // TODO(alexr): Use lambda named captures for // these cached values once it is available. const pid_t commandPid = s->pid(); const string _name = name; const Duration timeout = checkTimeout; const TaskID _taskId = taskId; return s->status() .after( timeout, [timeout, commandPid, _name, _taskId](Future<Option<int>> future) { future.discard(); if (commandPid != -1) { // Cleanup the external command process. VLOG(1) << "Killing the " << _name << " process '" << commandPid << "' for task '" << _taskId << "'"; os::killtree(commandPid, SIGKILL); } return Failure("Command timed out after " + stringify(timeout)); }) .then([](const Option<int>& exitCode) -> Future<int> { if (exitCode.isNone()) { return Failure("Failed to reap the command process"); } return exitCode.get(); }); }
// Try to extract sourcePath into directory. If sourcePath is // recognized as an archive it will be extracted and true returned; // if not recognized then false will be returned. An Error is // returned if the extraction command fails. static Try<bool> extract( const string& sourcePath, const string& destinationDirectory) { Try<Nothing> result = Nothing(); Option<Subprocess::IO> in = None(); Option<Subprocess::IO> out = None(); vector<string> command; // Extract any .tar, .tgz, tar.gz, tar.bz2 or zip files. if (strings::endsWith(sourcePath, ".tar") || strings::endsWith(sourcePath, ".tgz") || strings::endsWith(sourcePath, ".tar.gz") || strings::endsWith(sourcePath, ".tbz2") || strings::endsWith(sourcePath, ".tar.bz2") || strings::endsWith(sourcePath, ".txz") || strings::endsWith(sourcePath, ".tar.xz")) { command = {"tar", "-C", destinationDirectory, "-xf", sourcePath}; } else if (strings::endsWith(sourcePath, ".gz")) { string pathWithoutExtension = sourcePath.substr(0, sourcePath.length() - 3); string filename = Path(pathWithoutExtension).basename(); string destinationPath = path::join(destinationDirectory, filename); command = {"gunzip", "-d", "-c"}; in = Subprocess::PATH(sourcePath); out = Subprocess::PATH(destinationPath); } else if (strings::endsWith(sourcePath, ".zip")) { #ifdef __WINDOWS__ command = {"powershell", "-NoProfile", "-Command", "Expand-Archive", "-Force", "-Path", sourcePath, "-DestinationPath", destinationDirectory}; #else command = {"unzip", "-o", "-d", destinationDirectory, sourcePath}; #endif // __WINDOWS__ } else { return false; } CHECK_GT(command.size(), 0u); Try<Subprocess> extractProcess = subprocess( command[0], command, in.getOrElse(Subprocess::PATH(os::DEV_NULL)), out.getOrElse(Subprocess::FD(STDOUT_FILENO)), Subprocess::FD(STDERR_FILENO)); if (extractProcess.isError()) { return Error( "Failed to extract '" + sourcePath + "': '" + strings::join(" ", command) + "' failed: " + extractProcess.error()); } // `status()` never fails or gets discarded. int status = extractProcess->status()->get(); if (!WSUCCEEDED(status)) { return Error( "Failed to extract '" + sourcePath + "': '" + strings::join(" ", command) + "' failed: " + WSTRINGIFY(status)); } LOG(INFO) << "Extracted '" << sourcePath << "' into '" << destinationDirectory << "'"; return true; }
Future<Nothing> HealthCheckerProcess::_tcpHealthCheck() { CHECK_EQ(HealthCheck::TCP, check.type()); CHECK(check.has_tcp()); // TCP_CHECK_COMMAND should be reachable. CHECK(os::exists(launcherDir)); const HealthCheck::TCPCheckInfo& tcp = check.tcp(); VLOG(1) << "Launching TCP health check at port '" << tcp.port() << "'"; const string tcpConnectPath = path::join(launcherDir, TCP_CHECK_COMMAND); const vector<string> tcpConnectArguments = { tcpConnectPath, "--ip=" + DEFAULT_DOMAIN, "--port=" + stringify(tcp.port()) }; Try<Subprocess> s = subprocess( tcpConnectPath, tcpConnectArguments, Subprocess::PATH("/dev/null"), Subprocess::PIPE(), Subprocess::PIPE(), nullptr, None(), clone); if (s.isError()) { return Failure( "Failed to create the " + string(TCP_CHECK_COMMAND) + " subprocess: " + s.error()); } pid_t tcpConnectPid = s->pid(); Duration timeout = Seconds(static_cast<int64_t>(check.timeout_seconds())); return await( s->status(), process::io::read(s->out().get()), process::io::read(s->err().get())) .after(timeout, [timeout, tcpConnectPid](Future<tuple<Future<Option<int>>, Future<string>, Future<string>>> future) { future.discard(); if (tcpConnectPid != -1) { // Cleanup the TCP_CHECK_COMMAND process. VLOG(1) << "Killing the TCP health check process " << tcpConnectPid; os::killtree(tcpConnectPid, SIGKILL); } return Failure( string(TCP_CHECK_COMMAND) + " has not returned after " + stringify(timeout) + "; aborting"); }) .then(defer(self(), &Self::__tcpHealthCheck, lambda::_1)); }
Future<Nothing> HealthCheckerProcess::_httpHealthCheck() { CHECK_EQ(HealthCheck::HTTP, check.type()); CHECK(check.has_http()); const HealthCheck::HTTPCheckInfo& http = check.http(); const string scheme = http.has_scheme() ? http.scheme() : DEFAULT_HTTP_SCHEME; const string path = http.has_path() ? http.path() : ""; const string url = scheme + "://" + DEFAULT_DOMAIN + ":" + stringify(http.port()) + path; VLOG(1) << "Launching HTTP health check '" << url << "'"; const vector<string> argv = { HTTP_CHECK_COMMAND, "-s", // Don't show progress meter or error messages. "-S", // Makes curl show an error message if it fails. "-L", // Follows HTTP 3xx redirects. "-k", // Ignores SSL validation when scheme is https. "-w", "%{http_code}", // Displays HTTP response code on stdout. "-o", "/dev/null", // Ignores output. url }; Try<Subprocess> s = subprocess( HTTP_CHECK_COMMAND, argv, Subprocess::PATH("/dev/null"), Subprocess::PIPE(), Subprocess::PIPE(), nullptr, None(), clone); if (s.isError()) { return Failure( "Failed to create the " + string(HTTP_CHECK_COMMAND) + " subprocess: " + s.error()); } pid_t curlPid = s->pid(); Duration timeout = Seconds(static_cast<int64_t>(check.timeout_seconds())); return await( s->status(), process::io::read(s->out().get()), process::io::read(s->err().get())) .after(timeout, [timeout, curlPid](Future<tuple<Future<Option<int>>, Future<string>, Future<string>>> future) { future.discard(); if (curlPid != -1) { // Cleanup the HTTP_CHECK_COMMAND process. VLOG(1) << "Killing the HTTP health check process " << curlPid; os::killtree(curlPid, SIGKILL); } return Failure( string(HTTP_CHECK_COMMAND) + " has not returned after " + stringify(timeout) + "; aborting"); }) .then(defer(self(), &Self::__httpHealthCheck, lambda::_1)); }
Future<Nothing> HealthCheckerProcess::_commandHealthCheck() { CHECK_EQ(HealthCheck::COMMAND, check.type()); CHECK(check.has_command()); const CommandInfo& command = check.command(); map<string, string> environment = os::environment(); foreach (const Environment::Variable& variable, command.environment().variables()) { environment[variable.name()] = variable.value(); } // Launch the subprocess. Try<Subprocess> external = Error("Not launched"); if (command.shell()) { // Use the shell variant. VLOG(1) << "Launching command health check '" << command.value() << "'"; external = subprocess( command.value(), Subprocess::PATH("/dev/null"), Subprocess::FD(STDERR_FILENO), Subprocess::FD(STDERR_FILENO), environment, clone); } else { // Use the exec variant. vector<string> argv; foreach (const string& arg, command.arguments()) { argv.push_back(arg); } VLOG(1) << "Launching command health check [" << command.value() << ", " << strings::join(", ", argv) << "]"; external = subprocess( command.value(), argv, Subprocess::PATH("/dev/null"), Subprocess::FD(STDERR_FILENO), Subprocess::FD(STDERR_FILENO), nullptr, environment, clone); } if (external.isError()) { return Failure("Failed to create subprocess: " + external.error()); } pid_t commandPid = external->pid(); Duration timeout = Seconds(static_cast<int64_t>(check.timeout_seconds())); return external->status() .after(timeout, [timeout, commandPid](Future<Option<int>> future) { future.discard(); if (commandPid != -1) { // Cleanup the external command process. VLOG(1) << "Killing the command health check process " << commandPid; os::killtree(commandPid, SIGKILL); } return Failure( "Command has not returned after " + stringify(timeout) + "; aborting"); }) .then([](const Option<int>& status) -> Future<Nothing> { if (status.isNone()) { return Failure("Failed to reap the command process"); } int statusCode = status.get(); if (statusCode != 0) { return Failure("Command returned " + WSTRINGIFY(statusCode)); } return Nothing(); }); }