2 * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved
4 * Contact: Jan Olszak <j.olszak@samsung.com>
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License
21 * @author Jan Olszak (j.olszak@samsung.com)
22 * @brief Definition of the class for managing containers
27 #include "host-dbus-definitions.hpp"
28 #include "common-dbus-definitions.hpp"
29 #include "container-dbus-definitions.hpp"
30 #include "containers-manager.hpp"
31 #include "container-admin.hpp"
32 #include "exception.hpp"
34 #include "utils/paths.hpp"
35 #include "logger/logger.hpp"
36 #include "config/manager.hpp"
37 #include "dbus/exception.hpp"
38 #include "utils/fs.hpp"
39 #include "utils/img.hpp"
41 #include <boost/filesystem.hpp>
42 #include <boost/regex.hpp>
43 #include <boost/uuid/uuid.hpp>
44 #include <boost/uuid/uuid_io.hpp>
45 #include <boost/uuid/uuid_generators.hpp>
46 #include <boost/exception/diagnostic_information.hpp>
52 namespace security_containers {
57 bool regexMatchVector(const std::string& str, const std::vector<boost::regex>& v)
59 for (const boost::regex& toMatch: v) {
60 if (boost::regex_match(str, toMatch)) {
68 const std::string HOST_ID = "host";
69 const std::string CONTAINER_TEMPLATE_CONFIG_PATH = "template.conf";
70 const std::string CONTAINER_TEMPLATE_LIBVIRT_CONFIG_PATH = "template.xml";
71 const std::string CONTAINER_TEMPLATE_LIBVIRT_NETWORK_PATH = "template-network.xml";
72 const std::string CONTAINER_TEMPLATE_LIBVIRT_NETWORK_FILTER_PATH = "template-nwfilter.xml";
74 const boost::regex CONTAINER_NAME_REGEX("~NAME~");
75 const boost::regex CONTAINER_UUID_REGEX("~UUID~");
76 const boost::regex CONTAINER_IP_THIRD_OCTET_REGEX("~IP~");
78 const unsigned int CONTAINER_IP_BASE_THIRD_OCTET = 100;
82 ContainersManager::ContainersManager(const std::string& managerConfigPath): mDetachOnExit(false)
84 LOGD("Instantiating ContainersManager object...");
86 mConfigPath = managerConfigPath;
87 config::loadFromFile(mConfigPath, mConfig);
89 mProxyCallPolicy.reset(new ProxyCallPolicy(mConfig.proxyCallRules));
91 using namespace std::placeholders;
92 mHostConnection.setProxyCallCallback(bind(&ContainersManager::handleProxyCall,
93 this, HOST_ID, _1, _2, _3, _4, _5, _6, _7));
95 mHostConnection.setGetContainerDbusesCallback(bind(
96 &ContainersManager::handleGetContainerDbuses, this, _1));
98 mHostConnection.setGetContainerIdsCallback(bind(&ContainersManager::handleGetContainerIdsCall,
101 mHostConnection.setGetActiveContainerIdCallback(bind(&ContainersManager::handleGetActiveContainerIdCall,
104 mHostConnection.setSetActiveContainerCallback(bind(&ContainersManager::handleSetActiveContainerCall,
107 mHostConnection.setAddContainerCallback(bind(&ContainersManager::handleAddContainerCall,
110 for (auto& containerConfig : mConfig.containerConfigs) {
111 addContainer(containerConfig);
114 // check if default container exists, throw ContainerOperationException if not found
115 if (mContainers.find(mConfig.defaultId) == mContainers.end()) {
116 LOGE("Provided default container ID " << mConfig.defaultId << " is invalid.");
117 throw ContainerOperationException("Provided default container ID " + mConfig.defaultId +
121 LOGD("ContainersManager object instantiated");
123 if (mConfig.inputConfig.enabled) {
124 LOGI("Registering input monitor [" << mConfig.inputConfig.device.c_str() << "]");
125 mSwitchingSequenceMonitor.reset(
126 new InputMonitor(mConfig.inputConfig,
127 std::bind(&ContainersManager::switchingSequenceMonitorNotify,
134 ContainersManager::~ContainersManager()
136 LOGD("Destroying ContainersManager object...");
138 if (!mDetachOnExit) {
141 } catch (ServerException&) {
142 LOGE("Failed to stop all of the containers");
146 LOGD("ContainersManager object destroyed");
149 void ContainersManager::addContainer(const std::string& containerConfig)
151 std::string baseConfigPath = utils::dirName(mConfigPath);
152 std::string containerConfigPath = utils::getAbsolutePath(containerConfig, baseConfigPath);
154 LOGT("Creating Container " << containerConfigPath);
155 std::unique_ptr<Container> c(new Container(containerConfigPath,
156 mConfig.runMountPointPrefix));
157 const std::string id = c->getId();
159 throw ContainerOperationException("Cannot use reserved container ID");
162 using namespace std::placeholders;
163 c->setNotifyActiveContainerCallback(bind(&ContainersManager::notifyActiveContainerHandler,
166 c->setDisplayOffCallback(bind(&ContainersManager::displayOffHandler,
169 c->setFileMoveRequestCallback(bind(&ContainersManager::handleContainerMoveFileRequest,
170 this, id, _1, _2, _3));
172 c->setProxyCallCallback(bind(&ContainersManager::handleProxyCall,
173 this, id, _1, _2, _3, _4, _5, _6, _7));
175 c->setDbusStateChangedCallback(bind(&ContainersManager::handleDbusStateChanged,
178 mContainers.insert(ContainerMap::value_type(id, std::move(c)));
181 void ContainersManager::focus(const std::string& containerId)
183 /* try to access the object first to throw immediately if it doesn't exist */
184 ContainerMap::mapped_type& foregroundContainer = mContainers.at(containerId);
186 if (!foregroundContainer->activateVT()) {
187 LOGE("Failed to activate containers VT. Aborting focus.");
191 for (auto& container : mContainers) {
192 LOGD(container.second->getId() << ": being sent to background");
193 container.second->goBackground();
195 mConfig.foregroundId = foregroundContainer->getId();
196 LOGD(mConfig.foregroundId << ": being sent to foreground");
197 foregroundContainer->goForeground();
200 void ContainersManager::startAll()
202 LOGI("Starting all containers");
204 bool isForegroundFound = false;
206 for (auto& container : mContainers) {
207 container.second->start();
209 if (container.first == mConfig.foregroundId) {
210 isForegroundFound = true;
211 LOGI(container.second->getId() << ": set as the foreground container");
212 container.second->goForeground();
216 if (!isForegroundFound) {
217 auto foregroundIterator = std::min_element(mContainers.begin(), mContainers.end(),
218 [](ContainerMap::value_type &c1, ContainerMap::value_type &c2) {
219 return c1.second->getPrivilege() < c2.second->getPrivilege();
222 mConfig.foregroundId = foregroundIterator->second->getId();
223 LOGI(mConfig.foregroundId << ": no foreground container configured, setting one with highest priority");
224 foregroundIterator->second->goForeground();
228 void ContainersManager::stopAll()
230 LOGI("Stopping all containers");
232 for (auto& container : mContainers) {
233 container.second->stop();
237 std::string ContainersManager::getRunningForegroundContainerId()
239 for (auto& container : mContainers) {
240 if (container.first == mConfig.foregroundId &&
241 container.second->isRunning()) {
242 return container.first;
245 return std::string();
248 std::string ContainersManager::getNextToForegroundContainerId()
250 // handles case where there is no next container
251 if (mContainers.size() < 2) {
252 return std::string();
255 for (auto it = mContainers.begin(); it != mContainers.end(); ++it) {
256 if (it->first == mConfig.foregroundId &&
257 it->second->isRunning()) {
258 auto nextIt = std::next(it);
259 if (nextIt != mContainers.end()) {
260 return nextIt->first;
264 return mContainers.begin()->first;
267 void ContainersManager::switchingSequenceMonitorNotify()
269 LOGI("switchingSequenceMonitorNotify() called");
271 auto nextContainerId = getNextToForegroundContainerId();
272 focus(nextContainerId);
276 void ContainersManager::setContainersDetachOnExit()
278 mDetachOnExit = true;
280 for (auto& container : mContainers) {
281 container.second->setDetachOnExit();
285 void ContainersManager::notifyActiveContainerHandler(const std::string& caller,
286 const std::string& application,
287 const std::string& message)
289 LOGI("notifyActiveContainerHandler(" << caller << ", " << application << ", " << message
292 const std::string activeContainer = getRunningForegroundContainerId();
293 if (!activeContainer.empty() && caller != activeContainer) {
294 mContainers[activeContainer]->sendNotification(caller, application, message);
296 } catch(const SecurityContainersException&) {
297 LOGE("Notification from " << caller << " hasn't been sent");
301 void ContainersManager::displayOffHandler(const std::string& /*caller*/)
303 // get config of currently set container and switch if switchToDefaultAfterTimeout is true
304 const std::string activeContainerName = getRunningForegroundContainerId();
305 const auto& activeContainer = mContainers.find(activeContainerName);
307 if (activeContainer != mContainers.end() &&
308 activeContainer->second->isSwitchToDefaultAfterTimeoutAllowed()) {
309 LOGI("Switching to default container " << mConfig.defaultId);
310 focus(mConfig.defaultId);
314 void ContainersManager::handleContainerMoveFileRequest(const std::string& srcContainerId,
315 const std::string& dstContainerId,
316 const std::string& path,
317 dbus::MethodResultBuilder::Pointer result)
319 // TODO: this implementation is only a placeholder.
320 // There are too many unanswered questions and security concerns:
321 // 1. What about mount namespace, host might not see the source/destination
322 // file. The file might be a different file from a host perspective.
323 // 2. Copy vs move (speed and security concerns over already opened FDs)
324 // 3. Access to source and destination files - DAC, uid/gig
325 // 4. Access to source and destintation files - MAC, smack
326 // 5. Destination file uid/gid assignment
327 // 6. Destination file smack label assignment
328 // 7. Verifiability of the source path
330 // NOTE: other possible implementations include:
331 // 1. Sending file descriptors opened directly in each container through DBUS
332 // using something like g_dbus_message_set_unix_fd_list()
333 // 2. SCS forking and calling setns(MNT) in each container and opening files
334 // by itself, then passing FDs to the main process
335 // Now when the main process has obtained FDs (by either of those methods)
336 // it can do the copying by itself.
338 LOGI("File move requested\n"
339 << "src: " << srcContainerId << "\n"
340 << "dst: " << dstContainerId << "\n"
341 << "path: " << path);
343 ContainerMap::const_iterator srcIter = mContainers.find(srcContainerId);
344 if (srcIter == mContainers.end()) {
345 LOGE("Source container '" << srcContainerId << "' not found");
348 Container& srcContainer = *srcIter->second;
350 ContainerMap::const_iterator dstIter = mContainers.find(dstContainerId);
351 if (dstIter == mContainers.end()) {
352 LOGE("Destination container '" << dstContainerId << "' not found");
353 result->set(g_variant_new("(s)", api::container::FILE_MOVE_DESTINATION_NOT_FOUND.c_str()));
356 Container& dstContanier = *dstIter->second;
358 if (srcContainerId == dstContainerId) {
359 LOGE("Cannot send a file to yourself");
360 result->set(g_variant_new("(s)", api::container::FILE_MOVE_WRONG_DESTINATION.c_str()));
364 if (!regexMatchVector(path, srcContainer.getPermittedToSend())) {
365 LOGE("Source container has no permissions to send the file: " << path);
366 result->set(g_variant_new("(s)", api::container::FILE_MOVE_NO_PERMISSIONS_SEND.c_str()));
370 if (!regexMatchVector(path, dstContanier.getPermittedToRecv())) {
371 LOGE("Destination container has no permissions to receive the file: " << path);
372 result->set(g_variant_new("(s)", api::container::FILE_MOVE_NO_PERMISSIONS_RECEIVE.c_str()));
376 namespace fs = boost::filesystem;
377 std::string srcPath = fs::absolute(srcContainerId, mConfig.containersPath).string() + path;
378 std::string dstPath = fs::absolute(dstContainerId, mConfig.containersPath).string() + path;
380 if (!utils::moveFile(srcPath, dstPath)) {
381 LOGE("Failed to move the file: " << path);
382 result->set(g_variant_new("(s)", api::container::FILE_MOVE_FAILED.c_str()));
384 result->set(g_variant_new("(s)", api::container::FILE_MOVE_SUCCEEDED.c_str()));
386 dstContanier.sendNotification(srcContainerId, path, api::container::FILE_MOVE_SUCCEEDED);
387 } catch (ServerException&) {
388 LOGE("Notification to '" << dstContainerId << "' has not been sent");
393 void ContainersManager::handleProxyCall(const std::string& caller,
394 const std::string& target,
395 const std::string& targetBusName,
396 const std::string& targetObjectPath,
397 const std::string& targetInterface,
398 const std::string& targetMethod,
399 GVariant* parameters,
400 dbus::MethodResultBuilder::Pointer result)
402 if (!mProxyCallPolicy->isProxyCallAllowed(caller,
408 LOGW("Forbidden proxy call; " << caller << " -> " << target << "; " << targetBusName
409 << "; " << targetObjectPath << "; " << targetInterface << "; " << targetMethod);
410 result->setError(api::ERROR_FORBIDDEN, "Proxy call forbidden");
414 LOGI("Proxy call; " << caller << " -> " << target << "; " << targetBusName
415 << "; " << targetObjectPath << "; " << targetInterface << "; " << targetMethod);
417 auto asyncResultCallback = [result](dbus::AsyncMethodCallResult& asyncMethodCallResult) {
419 GVariant* targetResult = asyncMethodCallResult.get();
420 result->set(g_variant_new("(v)", targetResult));
421 } catch (dbus::DbusException& e) {
422 result->setError(api::ERROR_FORWARDED, e.what());
426 if (target == HOST_ID) {
427 mHostConnection.proxyCallAsync(targetBusName,
432 asyncResultCallback);
436 ContainerMap::const_iterator targetIter = mContainers.find(target);
437 if (targetIter == mContainers.end()) {
438 LOGE("Target container '" << target << "' not found");
439 result->setError(api::ERROR_UNKNOWN_ID, "Unknown proxy call target");
443 Container& targetContainer = *targetIter->second;
444 targetContainer.proxyCallAsync(targetBusName,
449 asyncResultCallback);
452 void ContainersManager::handleGetContainerDbuses(dbus::MethodResultBuilder::Pointer result)
454 std::vector<GVariant*> entries;
455 for (auto& container : mContainers) {
456 GVariant* containerId = g_variant_new_string(container.first.c_str());
457 GVariant* dbusAddress = g_variant_new_string(container.second->getDbusAddress().c_str());
458 GVariant* entry = g_variant_new_dict_entry(containerId, dbusAddress);
459 entries.push_back(entry);
461 GVariant* dict = g_variant_new_array(G_VARIANT_TYPE("{ss}"), entries.data(), entries.size());
462 result->set(g_variant_new("(*)", dict));
465 void ContainersManager::handleDbusStateChanged(const std::string& containerId,
466 const std::string& dbusAddress)
468 mHostConnection.signalContainerDbusState(containerId, dbusAddress);
471 void ContainersManager::handleGetContainerIdsCall(dbus::MethodResultBuilder::Pointer result)
473 std::vector<GVariant*> containerIds;
474 for(auto& container: mContainers){
475 containerIds.push_back(g_variant_new_string(container.first.c_str()));
478 GVariant* array = g_variant_new_array(G_VARIANT_TYPE("s"),
480 containerIds.size());
481 result->set(g_variant_new("(*)", array));
484 void ContainersManager::handleGetActiveContainerIdCall(dbus::MethodResultBuilder::Pointer result)
486 LOGI("GetActiveContainerId call");
487 if (mContainers[mConfig.foregroundId]->isRunning()){
488 result->set(g_variant_new("(s)", mConfig.foregroundId.c_str()));
490 result->set(g_variant_new("(s)", ""));
494 void ContainersManager::handleSetActiveContainerCall(const std::string& id,
495 dbus::MethodResultBuilder::Pointer result)
497 LOGI("SetActiveContainer call; Id=" << id );
498 auto container = mContainers.find(id);
499 if (container == mContainers.end()){
500 LOGE("No container with id=" << id );
501 result->setError(api::ERROR_UNKNOWN_ID, "No such container id");
505 if (container->second->isStopped()){
506 LOGE("Could not activate a stopped container");
507 result->setError(api::host::ERROR_CONTAINER_STOPPED,
508 "Could not activate a stopped container");
517 void ContainersManager::generateNewConfig(const std::string& id,
518 const std::string& templatePath,
519 const std::string& resultPath)
521 namespace fs = boost::filesystem;
523 std::string resultFileDir = utils::dirName(resultPath);
524 if (!fs::exists(resultFileDir)) {
525 if (!utils::createEmptyDir(resultFileDir)) {
526 LOGE("Unable to create directory for new config.");
527 throw ContainerOperationException("Unable to create directory for new config.");
531 fs::path resultFile(resultPath);
532 if (fs::exists(resultFile)) {
533 LOGT(resultPath << " already exists, removing");
534 fs::remove(resultFile);
538 if (!utils::readFileContent(templatePath, config)) {
539 LOGE("Failed to read template config file.");
540 throw ContainerOperationException("Failed to read template config file.");
543 std::string resultConfig = boost::regex_replace(config, CONTAINER_NAME_REGEX, id);
545 boost::uuids::uuid u = boost::uuids::random_generator()();
546 std::string uuidStr = to_string(u);
547 LOGD("uuid: " << uuidStr);
548 resultConfig = boost::regex_replace(resultConfig, CONTAINER_UUID_REGEX, uuidStr);
550 // generate third IP octet for network config
551 std::string thirdOctetStr = std::to_string(CONTAINER_IP_BASE_THIRD_OCTET + mContainers.size() + 1);
552 LOGD("ip_third_octet: " << thirdOctetStr);
553 resultConfig = boost::regex_replace(resultConfig, CONTAINER_IP_THIRD_OCTET_REGEX, thirdOctetStr);
555 if (!utils::saveFileContent(resultPath, resultConfig)) {
556 LOGE("Faield to save new config file.");
557 throw ContainerOperationException("Failed to save new config file.");
560 // restrict new config file so that only owner (security-containers) can write it
561 fs::permissions(resultPath, fs::perms::owner_all |
562 fs::perms::group_read |
563 fs::perms::others_read);
566 void ContainersManager::handleAddContainerCall(const std::string& id,
567 dbus::MethodResultBuilder::Pointer result)
569 LOGI("Adding container " << id);
571 // TODO: This solution is temporary. It utilizes direct access to config files when creating new
572 // containers. Update this handler when config database will appear.
573 namespace fs = boost::filesystem;
575 boost::system::error_code ec;
576 const std::string containerPathStr = utils::createFilePath(mConfig.containersPath, "/", id, "/");
578 // check if container does not exist
579 if (mContainers.find(id) != mContainers.end()) {
580 LOGE("Cannot create " << id << " container - already exists!");
581 result->setError(api::host::ERROR_CONTAINER_CREATE_FAILED,
582 "Cannot create " + id + " container - already exists!");
586 // copy container image if config contains path to image
587 LOGT("image path: " << mConfig.containerImagePath);
588 if (!mConfig.containerImagePath.empty()) {
589 if (!utils::copyImageContents(mConfig.containerImagePath, containerPathStr)) {
590 LOGE("Failed to copy container image.");
591 result->setError(api::host::ERROR_CONTAINER_CREATE_FAILED,
592 "Failed to copy container image.");
597 // generate paths to new configuration files
598 std::string baseDir = utils::dirName(mConfigPath);
599 std::string configDir = utils::getAbsolutePath(mConfig.containerNewConfigPrefix, baseDir);
600 std::string templateDir = utils::getAbsolutePath(mConfig.containerTemplatePath, baseDir);
602 std::string configPath = utils::createFilePath(templateDir, "/", CONTAINER_TEMPLATE_CONFIG_PATH);
603 std::string newConfigPath = utils::createFilePath(configDir, "/containers/", id + ".conf");
604 std::string libvirtConfigPath = utils::createFilePath(templateDir, "/", CONTAINER_TEMPLATE_LIBVIRT_CONFIG_PATH);
605 std::string newLibvirtConfigPath = utils::createFilePath(configDir, "/libvirt-config/", id + ".xml");
606 std::string libvirtNetworkPath = utils::createFilePath(templateDir, "/", CONTAINER_TEMPLATE_LIBVIRT_NETWORK_PATH);
607 std::string newLibvirtNetworkPath = utils::createFilePath(configDir, "/libvirt-config/", id + "-network.xml");
608 std::string libvirtNetworkFilterPath = utils::createFilePath(templateDir, "/", CONTAINER_TEMPLATE_LIBVIRT_NETWORK_FILTER_PATH);
609 std::string newLibvirtNetworkFilterPath = utils::createFilePath(configDir, "/libvirt-config/", id + "-nwfilter.xml");
611 auto removeAllWrapper = [](const std::string& path) {
613 LOGD("Removing copied data");
614 fs::remove_all(fs::path(path));
615 } catch(const boost::exception& e) {
616 LOGW("Failed to remove data: " << boost::diagnostic_information(e));
621 LOGI("Generating config from " << configPath << " to " << newConfigPath);
622 generateNewConfig(id, configPath, newConfigPath);
624 LOGI("Generating config from " << libvirtConfigPath << " to " << newLibvirtConfigPath);
625 generateNewConfig(id, libvirtConfigPath, newLibvirtConfigPath);
627 LOGI("Generating config from " << libvirtNetworkPath << " to " << newLibvirtNetworkPath);
628 generateNewConfig(id, libvirtNetworkPath, newLibvirtNetworkPath);
630 LOGI("Generating config from " << libvirtNetworkFilterPath << " to " << newLibvirtNetworkFilterPath);
631 generateNewConfig(id, libvirtNetworkFilterPath, newLibvirtNetworkFilterPath);
632 } catch (SecurityContainersException& e) {
634 removeAllWrapper(containerPathStr);
635 result->setError(api::host::ERROR_CONTAINER_CREATE_FAILED, e.what());
639 LOGT("Adding new container");
641 addContainer(newConfigPath);
642 } catch (SecurityContainersException& e) {
644 removeAllWrapper(containerPathStr);
645 result->setError(api::host::ERROR_CONTAINER_CREATE_FAILED, e.what());
649 auto resultCallback = [result, containerPathStr, removeAllWrapper](bool succeeded) {
653 LOGE("Failed to start container.");
654 removeAllWrapper(containerPathStr);
655 result->setError(api::host::ERROR_CONTAINER_CREATE_FAILED,
656 "Failed to start container.");
659 mContainers[id]->startAsync(resultCallback);
662 } // namespace security_containers