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 (!mConfig.defaultId.empty() && 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 if (foregroundIterator != mContainers.end()) {
223 mConfig.foregroundId = foregroundIterator->second->getId();
224 LOGI(mConfig.foregroundId << ": no foreground container configured, setting one with highest priority");
225 foregroundIterator->second->goForeground();
230 void ContainersManager::stopAll()
232 LOGI("Stopping all containers");
234 for (auto& container : mContainers) {
235 container.second->stop();
239 std::string ContainersManager::getRunningForegroundContainerId()
241 for (auto& container : mContainers) {
242 if (container.first == mConfig.foregroundId &&
243 container.second->isRunning()) {
244 return container.first;
247 return std::string();
250 std::string ContainersManager::getNextToForegroundContainerId()
252 // handles case where there is no next container
253 if (mContainers.size() < 2) {
254 return std::string();
257 for (auto it = mContainers.begin(); it != mContainers.end(); ++it) {
258 if (it->first == mConfig.foregroundId &&
259 it->second->isRunning()) {
260 auto nextIt = std::next(it);
261 if (nextIt != mContainers.end()) {
262 return nextIt->first;
266 return mContainers.begin()->first;
269 void ContainersManager::switchingSequenceMonitorNotify()
271 LOGI("switchingSequenceMonitorNotify() called");
273 auto nextContainerId = getNextToForegroundContainerId();
275 if (!nextContainerId.empty()) {
276 focus(nextContainerId);
281 void ContainersManager::setContainersDetachOnExit()
283 mDetachOnExit = true;
285 for (auto& container : mContainers) {
286 container.second->setDetachOnExit();
290 void ContainersManager::notifyActiveContainerHandler(const std::string& caller,
291 const std::string& application,
292 const std::string& message)
294 LOGI("notifyActiveContainerHandler(" << caller << ", " << application << ", " << message
297 const std::string activeContainer = getRunningForegroundContainerId();
298 if (!activeContainer.empty() && caller != activeContainer) {
299 mContainers[activeContainer]->sendNotification(caller, application, message);
301 } catch(const SecurityContainersException&) {
302 LOGE("Notification from " << caller << " hasn't been sent");
306 void ContainersManager::displayOffHandler(const std::string& /*caller*/)
308 // get config of currently set container and switch if switchToDefaultAfterTimeout is true
309 const std::string activeContainerName = getRunningForegroundContainerId();
310 const auto& activeContainer = mContainers.find(activeContainerName);
312 if (activeContainer != mContainers.end() &&
313 activeContainer->second->isSwitchToDefaultAfterTimeoutAllowed()) {
314 LOGI("Switching to default container " << mConfig.defaultId);
315 focus(mConfig.defaultId);
319 void ContainersManager::handleContainerMoveFileRequest(const std::string& srcContainerId,
320 const std::string& dstContainerId,
321 const std::string& path,
322 dbus::MethodResultBuilder::Pointer result)
324 // TODO: this implementation is only a placeholder.
325 // There are too many unanswered questions and security concerns:
326 // 1. What about mount namespace, host might not see the source/destination
327 // file. The file might be a different file from a host perspective.
328 // 2. Copy vs move (speed and security concerns over already opened FDs)
329 // 3. Access to source and destination files - DAC, uid/gig
330 // 4. Access to source and destintation files - MAC, smack
331 // 5. Destination file uid/gid assignment
332 // 6. Destination file smack label assignment
333 // 7. Verifiability of the source path
335 // NOTE: other possible implementations include:
336 // 1. Sending file descriptors opened directly in each container through DBUS
337 // using something like g_dbus_message_set_unix_fd_list()
338 // 2. SCS forking and calling setns(MNT) in each container and opening files
339 // by itself, then passing FDs to the main process
340 // Now when the main process has obtained FDs (by either of those methods)
341 // it can do the copying by itself.
343 LOGI("File move requested\n"
344 << "src: " << srcContainerId << "\n"
345 << "dst: " << dstContainerId << "\n"
346 << "path: " << path);
348 ContainerMap::const_iterator srcIter = mContainers.find(srcContainerId);
349 if (srcIter == mContainers.end()) {
350 LOGE("Source container '" << srcContainerId << "' not found");
353 Container& srcContainer = *srcIter->second;
355 ContainerMap::const_iterator dstIter = mContainers.find(dstContainerId);
356 if (dstIter == mContainers.end()) {
357 LOGE("Destination container '" << dstContainerId << "' not found");
358 result->set(g_variant_new("(s)", api::container::FILE_MOVE_DESTINATION_NOT_FOUND.c_str()));
361 Container& dstContanier = *dstIter->second;
363 if (srcContainerId == dstContainerId) {
364 LOGE("Cannot send a file to yourself");
365 result->set(g_variant_new("(s)", api::container::FILE_MOVE_WRONG_DESTINATION.c_str()));
369 if (!regexMatchVector(path, srcContainer.getPermittedToSend())) {
370 LOGE("Source container has no permissions to send the file: " << path);
371 result->set(g_variant_new("(s)", api::container::FILE_MOVE_NO_PERMISSIONS_SEND.c_str()));
375 if (!regexMatchVector(path, dstContanier.getPermittedToRecv())) {
376 LOGE("Destination container has no permissions to receive the file: " << path);
377 result->set(g_variant_new("(s)", api::container::FILE_MOVE_NO_PERMISSIONS_RECEIVE.c_str()));
381 namespace fs = boost::filesystem;
382 std::string srcPath = fs::absolute(srcContainerId, mConfig.containersPath).string() + path;
383 std::string dstPath = fs::absolute(dstContainerId, mConfig.containersPath).string() + path;
385 if (!utils::moveFile(srcPath, dstPath)) {
386 LOGE("Failed to move the file: " << path);
387 result->set(g_variant_new("(s)", api::container::FILE_MOVE_FAILED.c_str()));
389 result->set(g_variant_new("(s)", api::container::FILE_MOVE_SUCCEEDED.c_str()));
391 dstContanier.sendNotification(srcContainerId, path, api::container::FILE_MOVE_SUCCEEDED);
392 } catch (ServerException&) {
393 LOGE("Notification to '" << dstContainerId << "' has not been sent");
398 void ContainersManager::handleProxyCall(const std::string& caller,
399 const std::string& target,
400 const std::string& targetBusName,
401 const std::string& targetObjectPath,
402 const std::string& targetInterface,
403 const std::string& targetMethod,
404 GVariant* parameters,
405 dbus::MethodResultBuilder::Pointer result)
407 if (!mProxyCallPolicy->isProxyCallAllowed(caller,
413 LOGW("Forbidden proxy call; " << caller << " -> " << target << "; " << targetBusName
414 << "; " << targetObjectPath << "; " << targetInterface << "; " << targetMethod);
415 result->setError(api::ERROR_FORBIDDEN, "Proxy call forbidden");
419 LOGI("Proxy call; " << caller << " -> " << target << "; " << targetBusName
420 << "; " << targetObjectPath << "; " << targetInterface << "; " << targetMethod);
422 auto asyncResultCallback = [result](dbus::AsyncMethodCallResult& asyncMethodCallResult) {
424 GVariant* targetResult = asyncMethodCallResult.get();
425 result->set(g_variant_new("(v)", targetResult));
426 } catch (dbus::DbusException& e) {
427 result->setError(api::ERROR_FORWARDED, e.what());
431 if (target == HOST_ID) {
432 mHostConnection.proxyCallAsync(targetBusName,
437 asyncResultCallback);
441 ContainerMap::const_iterator targetIter = mContainers.find(target);
442 if (targetIter == mContainers.end()) {
443 LOGE("Target container '" << target << "' not found");
444 result->setError(api::ERROR_UNKNOWN_ID, "Unknown proxy call target");
448 Container& targetContainer = *targetIter->second;
449 targetContainer.proxyCallAsync(targetBusName,
454 asyncResultCallback);
457 void ContainersManager::handleGetContainerDbuses(dbus::MethodResultBuilder::Pointer result)
459 std::vector<GVariant*> entries;
460 for (auto& container : mContainers) {
461 GVariant* containerId = g_variant_new_string(container.first.c_str());
462 GVariant* dbusAddress = g_variant_new_string(container.second->getDbusAddress().c_str());
463 GVariant* entry = g_variant_new_dict_entry(containerId, dbusAddress);
464 entries.push_back(entry);
466 GVariant* dict = g_variant_new_array(G_VARIANT_TYPE("{ss}"), entries.data(), entries.size());
467 result->set(g_variant_new("(*)", dict));
470 void ContainersManager::handleDbusStateChanged(const std::string& containerId,
471 const std::string& dbusAddress)
473 mHostConnection.signalContainerDbusState(containerId, dbusAddress);
476 void ContainersManager::handleGetContainerIdsCall(dbus::MethodResultBuilder::Pointer result)
478 std::vector<GVariant*> containerIds;
479 for(auto& container: mContainers){
480 containerIds.push_back(g_variant_new_string(container.first.c_str()));
483 GVariant* array = g_variant_new_array(G_VARIANT_TYPE("s"),
485 containerIds.size());
486 result->set(g_variant_new("(*)", array));
489 void ContainersManager::handleGetActiveContainerIdCall(dbus::MethodResultBuilder::Pointer result)
491 LOGI("GetActiveContainerId call");
492 if (mContainers[mConfig.foregroundId]->isRunning()){
493 result->set(g_variant_new("(s)", mConfig.foregroundId.c_str()));
495 result->set(g_variant_new("(s)", ""));
499 void ContainersManager::handleSetActiveContainerCall(const std::string& id,
500 dbus::MethodResultBuilder::Pointer result)
502 LOGI("SetActiveContainer call; Id=" << id );
503 auto container = mContainers.find(id);
504 if (container == mContainers.end()){
505 LOGE("No container with id=" << id );
506 result->setError(api::ERROR_UNKNOWN_ID, "No such container id");
510 if (container->second->isStopped()){
511 LOGE("Could not activate a stopped container");
512 result->setError(api::host::ERROR_CONTAINER_STOPPED,
513 "Could not activate a stopped container");
522 void ContainersManager::generateNewConfig(const std::string& id,
523 const std::string& templatePath,
524 const std::string& resultPath)
526 namespace fs = boost::filesystem;
528 std::string resultFileDir = utils::dirName(resultPath);
529 if (!fs::exists(resultFileDir)) {
530 if (!utils::createEmptyDir(resultFileDir)) {
531 LOGE("Unable to create directory for new config.");
532 throw ContainerOperationException("Unable to create directory for new config.");
536 fs::path resultFile(resultPath);
537 if (fs::exists(resultFile)) {
538 LOGT(resultPath << " already exists, removing");
539 fs::remove(resultFile);
543 if (!utils::readFileContent(templatePath, config)) {
544 LOGE("Failed to read template config file.");
545 throw ContainerOperationException("Failed to read template config file.");
548 std::string resultConfig = boost::regex_replace(config, CONTAINER_NAME_REGEX, id);
550 boost::uuids::uuid u = boost::uuids::random_generator()();
551 std::string uuidStr = to_string(u);
552 LOGD("uuid: " << uuidStr);
553 resultConfig = boost::regex_replace(resultConfig, CONTAINER_UUID_REGEX, uuidStr);
555 // generate third IP octet for network config
556 std::string thirdOctetStr = std::to_string(CONTAINER_IP_BASE_THIRD_OCTET + mContainers.size() + 1);
557 LOGD("ip_third_octet: " << thirdOctetStr);
558 resultConfig = boost::regex_replace(resultConfig, CONTAINER_IP_THIRD_OCTET_REGEX, thirdOctetStr);
560 if (!utils::saveFileContent(resultPath, resultConfig)) {
561 LOGE("Faield to save new config file.");
562 throw ContainerOperationException("Failed to save new config file.");
565 // restrict new config file so that only owner (security-containers) can write it
566 fs::permissions(resultPath, fs::perms::owner_all |
567 fs::perms::group_read |
568 fs::perms::others_read);
571 void ContainersManager::handleAddContainerCall(const std::string& id,
572 dbus::MethodResultBuilder::Pointer result)
574 LOGI("Adding container " << id);
576 // TODO: This solution is temporary. It utilizes direct access to config files when creating new
577 // containers. Update this handler when config database will appear.
578 namespace fs = boost::filesystem;
580 boost::system::error_code ec;
581 const std::string containerPathStr = utils::createFilePath(mConfig.containersPath, "/", id, "/");
583 // check if container does not exist
584 if (mContainers.find(id) != mContainers.end()) {
585 LOGE("Cannot create " << id << " container - already exists!");
586 result->setError(api::host::ERROR_CONTAINER_CREATE_FAILED,
587 "Cannot create " + id + " container - already exists!");
591 // copy container image if config contains path to image
592 LOGT("image path: " << mConfig.containerImagePath);
593 if (!mConfig.containerImagePath.empty()) {
594 if (!utils::copyImageContents(mConfig.containerImagePath, containerPathStr)) {
595 LOGE("Failed to copy container image.");
596 result->setError(api::host::ERROR_CONTAINER_CREATE_FAILED,
597 "Failed to copy container image.");
602 // generate paths to new configuration files
603 std::string baseDir = utils::dirName(mConfigPath);
604 std::string configDir = utils::getAbsolutePath(mConfig.containerNewConfigPrefix, baseDir);
605 std::string templateDir = utils::getAbsolutePath(mConfig.containerTemplatePath, baseDir);
607 std::string configPath = utils::createFilePath(templateDir, "/", CONTAINER_TEMPLATE_CONFIG_PATH);
608 std::string newConfigPath = utils::createFilePath(configDir, "/containers/", id + ".conf");
609 std::string libvirtConfigPath = utils::createFilePath(templateDir, "/", CONTAINER_TEMPLATE_LIBVIRT_CONFIG_PATH);
610 std::string newLibvirtConfigPath = utils::createFilePath(configDir, "/libvirt-config/", id + ".xml");
611 std::string libvirtNetworkPath = utils::createFilePath(templateDir, "/", CONTAINER_TEMPLATE_LIBVIRT_NETWORK_PATH);
612 std::string newLibvirtNetworkPath = utils::createFilePath(configDir, "/libvirt-config/", id + "-network.xml");
613 std::string libvirtNetworkFilterPath = utils::createFilePath(templateDir, "/", CONTAINER_TEMPLATE_LIBVIRT_NETWORK_FILTER_PATH);
614 std::string newLibvirtNetworkFilterPath = utils::createFilePath(configDir, "/libvirt-config/", id + "-nwfilter.xml");
616 auto removeAllWrapper = [](const std::string& path) {
618 LOGD("Removing copied data");
619 fs::remove_all(fs::path(path));
620 } catch(const boost::exception& e) {
621 LOGW("Failed to remove data: " << boost::diagnostic_information(e));
626 LOGI("Generating config from " << configPath << " to " << newConfigPath);
627 generateNewConfig(id, configPath, newConfigPath);
629 LOGI("Generating config from " << libvirtConfigPath << " to " << newLibvirtConfigPath);
630 generateNewConfig(id, libvirtConfigPath, newLibvirtConfigPath);
632 LOGI("Generating config from " << libvirtNetworkPath << " to " << newLibvirtNetworkPath);
633 generateNewConfig(id, libvirtNetworkPath, newLibvirtNetworkPath);
635 LOGI("Generating config from " << libvirtNetworkFilterPath << " to " << newLibvirtNetworkFilterPath);
636 generateNewConfig(id, libvirtNetworkFilterPath, newLibvirtNetworkFilterPath);
637 } catch (SecurityContainersException& e) {
639 removeAllWrapper(containerPathStr);
640 result->setError(api::host::ERROR_CONTAINER_CREATE_FAILED, e.what());
644 LOGT("Adding new container");
646 addContainer(newConfigPath);
647 } catch (SecurityContainersException& e) {
649 removeAllWrapper(containerPathStr);
650 result->setError(api::host::ERROR_CONTAINER_CREATE_FAILED, e.what());
654 auto resultCallback = [result, containerPathStr, removeAllWrapper](bool succeeded) {
658 LOGE("Failed to start container.");
659 removeAllWrapper(containerPathStr);
660 result->setError(api::host::ERROR_CONTAINER_CREATE_FAILED,
661 "Failed to start container.");
664 mContainers[id]->startAsync(resultCallback);
667 } // namespace security_containers