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 for (auto& container : mContainers) {
187 LOGD(container.second->getId() << ": being sent to background");
188 container.second->goBackground();
190 mConfig.foregroundId = foregroundContainer->getId();
191 LOGD(mConfig.foregroundId << ": being sent to foreground");
192 foregroundContainer->goForeground();
195 void ContainersManager::startAll()
197 LOGI("Starting all containers");
199 bool isForegroundFound = false;
201 for (auto& container : mContainers) {
202 container.second->start();
204 if (container.first == mConfig.foregroundId) {
205 isForegroundFound = true;
206 LOGI(container.second->getId() << ": set as the foreground container");
207 container.second->goForeground();
211 if (!isForegroundFound) {
212 auto foregroundIterator = std::min_element(mContainers.begin(), mContainers.end(),
213 [](ContainerMap::value_type &c1, ContainerMap::value_type &c2) {
214 return c1.second->getPrivilege() < c2.second->getPrivilege();
217 mConfig.foregroundId = foregroundIterator->second->getId();
218 LOGI(mConfig.foregroundId << ": no foreground container configured, setting one with highest priority");
219 foregroundIterator->second->goForeground();
223 void ContainersManager::stopAll()
225 LOGI("Stopping all containers");
227 for (auto& container : mContainers) {
228 container.second->stop();
232 std::string ContainersManager::getRunningForegroundContainerId()
234 for (auto& container : mContainers) {
235 if (container.first == mConfig.foregroundId &&
236 container.second->isRunning()) {
237 return container.first;
240 return std::string();
243 void ContainersManager::switchingSequenceMonitorNotify()
245 LOGI("switchingSequenceMonitorNotify() called");
250 void ContainersManager::setContainersDetachOnExit()
252 mDetachOnExit = true;
254 for (auto& container : mContainers) {
255 container.second->setDetachOnExit();
259 void ContainersManager::notifyActiveContainerHandler(const std::string& caller,
260 const std::string& application,
261 const std::string& message)
263 LOGI("notifyActiveContainerHandler(" << caller << ", " << application << ", " << message
266 const std::string activeContainer = getRunningForegroundContainerId();
267 if (!activeContainer.empty() && caller != activeContainer) {
268 mContainers[activeContainer]->sendNotification(caller, application, message);
270 } catch(const SecurityContainersException&) {
271 LOGE("Notification from " << caller << " hasn't been sent");
275 void ContainersManager::displayOffHandler(const std::string& /*caller*/)
277 // get config of currently set container and switch if switchToDefaultAfterTimeout is true
278 const std::string activeContainerName = getRunningForegroundContainerId();
279 const auto& activeContainer = mContainers.find(activeContainerName);
281 if (activeContainer != mContainers.end() &&
282 activeContainer->second->isSwitchToDefaultAfterTimeoutAllowed()) {
283 LOGI("Switching to default container " << mConfig.defaultId);
284 focus(mConfig.defaultId);
288 void ContainersManager::handleContainerMoveFileRequest(const std::string& srcContainerId,
289 const std::string& dstContainerId,
290 const std::string& path,
291 dbus::MethodResultBuilder::Pointer result)
293 // TODO: this implementation is only a placeholder.
294 // There are too many unanswered questions and security concerns:
295 // 1. What about mount namespace, host might not see the source/destination
296 // file. The file might be a different file from a host perspective.
297 // 2. Copy vs move (speed and security concerns over already opened FDs)
298 // 3. Access to source and destination files - DAC, uid/gig
299 // 4. Access to source and destintation files - MAC, smack
300 // 5. Destination file uid/gid assignment
301 // 6. Destination file smack label assignment
302 // 7. Verifiability of the source path
304 // NOTE: other possible implementations include:
305 // 1. Sending file descriptors opened directly in each container through DBUS
306 // using something like g_dbus_message_set_unix_fd_list()
307 // 2. SCS forking and calling setns(MNT) in each container and opening files
308 // by itself, then passing FDs to the main process
309 // Now when the main process has obtained FDs (by either of those methods)
310 // it can do the copying by itself.
312 LOGI("File move requested\n"
313 << "src: " << srcContainerId << "\n"
314 << "dst: " << dstContainerId << "\n"
315 << "path: " << path);
317 ContainerMap::const_iterator srcIter = mContainers.find(srcContainerId);
318 if (srcIter == mContainers.end()) {
319 LOGE("Source container '" << srcContainerId << "' not found");
322 Container& srcContainer = *srcIter->second;
324 ContainerMap::const_iterator dstIter = mContainers.find(dstContainerId);
325 if (dstIter == mContainers.end()) {
326 LOGE("Destination container '" << dstContainerId << "' not found");
327 result->set(g_variant_new("(s)", api::container::FILE_MOVE_DESTINATION_NOT_FOUND.c_str()));
330 Container& dstContanier = *dstIter->second;
332 if (srcContainerId == dstContainerId) {
333 LOGE("Cannot send a file to yourself");
334 result->set(g_variant_new("(s)", api::container::FILE_MOVE_WRONG_DESTINATION.c_str()));
338 if (!regexMatchVector(path, srcContainer.getPermittedToSend())) {
339 LOGE("Source container has no permissions to send the file: " << path);
340 result->set(g_variant_new("(s)", api::container::FILE_MOVE_NO_PERMISSIONS_SEND.c_str()));
344 if (!regexMatchVector(path, dstContanier.getPermittedToRecv())) {
345 LOGE("Destination container has no permissions to receive the file: " << path);
346 result->set(g_variant_new("(s)", api::container::FILE_MOVE_NO_PERMISSIONS_RECEIVE.c_str()));
350 namespace fs = boost::filesystem;
351 std::string srcPath = fs::absolute(srcContainerId, mConfig.containersPath).string() + path;
352 std::string dstPath = fs::absolute(dstContainerId, mConfig.containersPath).string() + path;
354 if (!utils::moveFile(srcPath, dstPath)) {
355 LOGE("Failed to move the file: " << path);
356 result->set(g_variant_new("(s)", api::container::FILE_MOVE_FAILED.c_str()));
358 result->set(g_variant_new("(s)", api::container::FILE_MOVE_SUCCEEDED.c_str()));
360 dstContanier.sendNotification(srcContainerId, path, api::container::FILE_MOVE_SUCCEEDED);
361 } catch (ServerException&) {
362 LOGE("Notification to '" << dstContainerId << "' has not been sent");
367 void ContainersManager::handleProxyCall(const std::string& caller,
368 const std::string& target,
369 const std::string& targetBusName,
370 const std::string& targetObjectPath,
371 const std::string& targetInterface,
372 const std::string& targetMethod,
373 GVariant* parameters,
374 dbus::MethodResultBuilder::Pointer result)
376 if (!mProxyCallPolicy->isProxyCallAllowed(caller,
382 LOGW("Forbidden proxy call; " << caller << " -> " << target << "; " << targetBusName
383 << "; " << targetObjectPath << "; " << targetInterface << "; " << targetMethod);
384 result->setError(api::ERROR_FORBIDDEN, "Proxy call forbidden");
388 LOGI("Proxy call; " << caller << " -> " << target << "; " << targetBusName
389 << "; " << targetObjectPath << "; " << targetInterface << "; " << targetMethod);
391 auto asyncResultCallback = [result](dbus::AsyncMethodCallResult& asyncMethodCallResult) {
393 GVariant* targetResult = asyncMethodCallResult.get();
394 result->set(g_variant_new("(v)", targetResult));
395 } catch (dbus::DbusException& e) {
396 result->setError(api::ERROR_FORWARDED, e.what());
400 if (target == HOST_ID) {
401 mHostConnection.proxyCallAsync(targetBusName,
406 asyncResultCallback);
410 ContainerMap::const_iterator targetIter = mContainers.find(target);
411 if (targetIter == mContainers.end()) {
412 LOGE("Target container '" << target << "' not found");
413 result->setError(api::ERROR_UNKNOWN_ID, "Unknown proxy call target");
417 Container& targetContainer = *targetIter->second;
418 targetContainer.proxyCallAsync(targetBusName,
423 asyncResultCallback);
426 void ContainersManager::handleGetContainerDbuses(dbus::MethodResultBuilder::Pointer result)
428 std::vector<GVariant*> entries;
429 for (auto& container : mContainers) {
430 GVariant* containerId = g_variant_new_string(container.first.c_str());
431 GVariant* dbusAddress = g_variant_new_string(container.second->getDbusAddress().c_str());
432 GVariant* entry = g_variant_new_dict_entry(containerId, dbusAddress);
433 entries.push_back(entry);
435 GVariant* dict = g_variant_new_array(G_VARIANT_TYPE("{ss}"), entries.data(), entries.size());
436 result->set(g_variant_new("(*)", dict));
439 void ContainersManager::handleDbusStateChanged(const std::string& containerId,
440 const std::string& dbusAddress)
442 mHostConnection.signalContainerDbusState(containerId, dbusAddress);
445 void ContainersManager::handleGetContainerIdsCall(dbus::MethodResultBuilder::Pointer result)
447 std::vector<GVariant*> containerIds;
448 for(auto& container: mContainers){
449 containerIds.push_back(g_variant_new_string(container.first.c_str()));
452 GVariant* array = g_variant_new_array(G_VARIANT_TYPE("s"),
454 containerIds.size());
455 result->set(g_variant_new("(*)", array));
458 void ContainersManager::handleGetActiveContainerIdCall(dbus::MethodResultBuilder::Pointer result)
460 LOGI("GetActiveContainerId call");
461 if (mContainers[mConfig.foregroundId]->isRunning()){
462 result->set(g_variant_new("(s)", mConfig.foregroundId.c_str()));
464 result->set(g_variant_new("(s)", ""));
468 void ContainersManager::handleSetActiveContainerCall(const std::string& id,
469 dbus::MethodResultBuilder::Pointer result)
471 LOGI("SetActiveContainer call; Id=" << id );
472 auto container = mContainers.find(id);
473 if (container == mContainers.end()){
474 LOGE("No container with id=" << id );
475 result->setError(api::ERROR_UNKNOWN_ID, "No such container id");
479 if (container->second->isStopped()){
480 LOGE("Could not activate a stopped container");
481 result->setError(api::host::ERROR_CONTAINER_STOPPED,
482 "Could not activate a stopped container");
491 void ContainersManager::generateNewConfig(const std::string& id,
492 const std::string& templatePath,
493 const std::string& resultPath)
495 namespace fs = boost::filesystem;
497 std::string resultFileDir = utils::dirName(resultPath);
498 if (!fs::exists(resultFileDir)) {
499 if (!utils::createEmptyDir(resultFileDir)) {
500 LOGE("Unable to create directory for new config.");
501 throw ContainerOperationException("Unable to create directory for new config.");
505 fs::path resultFile(resultPath);
506 if (fs::exists(resultFile)) {
507 LOGT(resultPath << " already exists, removing");
508 fs::remove(resultFile);
512 if (!utils::readFileContent(templatePath, config)) {
513 LOGE("Failed to read template config file.");
514 throw ContainerOperationException("Failed to read template config file.");
517 std::string resultConfig = boost::regex_replace(config, CONTAINER_NAME_REGEX, id);
519 boost::uuids::uuid u = boost::uuids::random_generator()();
520 std::string uuidStr = to_string(u);
521 LOGD("uuid: " << uuidStr);
522 resultConfig = boost::regex_replace(resultConfig, CONTAINER_UUID_REGEX, uuidStr);
524 // generate third IP octet for network config
525 std::string thirdOctetStr = std::to_string(CONTAINER_IP_BASE_THIRD_OCTET + mContainers.size() + 1);
526 LOGD("ip_third_octet: " << thirdOctetStr);
527 resultConfig = boost::regex_replace(resultConfig, CONTAINER_IP_THIRD_OCTET_REGEX, thirdOctetStr);
529 if (!utils::saveFileContent(resultPath, resultConfig)) {
530 LOGE("Faield to save new config file.");
531 throw ContainerOperationException("Failed to save new config file.");
534 // restrict new config file so that only owner (security-containers) can write it
535 fs::permissions(resultPath, fs::perms::owner_all |
536 fs::perms::group_read |
537 fs::perms::others_read);
540 void ContainersManager::handleAddContainerCall(const std::string& id,
541 dbus::MethodResultBuilder::Pointer result)
543 LOGI("Adding container " << id);
545 // TODO: This solution is temporary. It utilizes direct access to config files when creating new
546 // containers. Update this handler when config database will appear.
547 namespace fs = boost::filesystem;
549 boost::system::error_code ec;
550 const std::string containerPathStr = utils::createFilePath(mConfig.containersPath, "/", id, "/");
552 // check if container does not exist
553 if (mContainers.find(id) != mContainers.end()) {
554 LOGE("Cannot create " << id << " container - already exists!");
555 result->setError(api::host::ERROR_CONTAINER_CREATE_FAILED,
556 "Cannot create " + id + " container - already exists!");
560 // copy container image if config contains path to image
561 LOGT("image path: " << mConfig.containerImagePath);
562 if (!mConfig.containerImagePath.empty()) {
563 if (!utils::copyImageContents(mConfig.containerImagePath, containerPathStr)) {
564 LOGE("Failed to copy container image.");
565 result->setError(api::host::ERROR_CONTAINER_CREATE_FAILED,
566 "Failed to copy container image.");
571 // generate paths to new configuration files
572 std::string baseDir = utils::dirName(mConfigPath);
573 std::string configDir = utils::getAbsolutePath(mConfig.containerNewConfigPrefix, baseDir);
574 std::string templateDir = utils::getAbsolutePath(mConfig.containerTemplatePath, baseDir);
576 std::string configPath = utils::createFilePath(templateDir, "/", CONTAINER_TEMPLATE_CONFIG_PATH);
577 std::string newConfigPath = utils::createFilePath(configDir, "/containers/", id + ".conf");
578 std::string libvirtConfigPath = utils::createFilePath(templateDir, "/", CONTAINER_TEMPLATE_LIBVIRT_CONFIG_PATH);
579 std::string newLibvirtConfigPath = utils::createFilePath(configDir, "/libvirt-config/", id + ".xml");
580 std::string libvirtNetworkPath = utils::createFilePath(templateDir, "/", CONTAINER_TEMPLATE_LIBVIRT_NETWORK_PATH);
581 std::string newLibvirtNetworkPath = utils::createFilePath(configDir, "/libvirt-config/", id + "-network.xml");
582 std::string libvirtNetworkFilterPath = utils::createFilePath(templateDir, "/", CONTAINER_TEMPLATE_LIBVIRT_NETWORK_FILTER_PATH);
583 std::string newLibvirtNetworkFilterPath = utils::createFilePath(configDir, "/libvirt-config/", id + "-nwfilter.xml");
585 auto removeAllWrapper = [](const std::string& path) {
587 LOGD("Removing copied data");
588 fs::remove_all(fs::path(path));
589 } catch(const boost::exception& e) {
590 LOGW("Failed to remove data: " << boost::diagnostic_information(e));
595 LOGI("Generating config from " << configPath << " to " << newConfigPath);
596 generateNewConfig(id, configPath, newConfigPath);
598 LOGI("Generating config from " << libvirtConfigPath << " to " << newLibvirtConfigPath);
599 generateNewConfig(id, libvirtConfigPath, newLibvirtConfigPath);
601 LOGI("Generating config from " << libvirtNetworkPath << " to " << newLibvirtNetworkPath);
602 generateNewConfig(id, libvirtNetworkPath, newLibvirtNetworkPath);
604 LOGI("Generating config from " << libvirtNetworkFilterPath << " to " << newLibvirtNetworkFilterPath);
605 generateNewConfig(id, libvirtNetworkFilterPath, newLibvirtNetworkFilterPath);
606 } catch (SecurityContainersException& e) {
608 removeAllWrapper(containerPathStr);
609 result->setError(api::host::ERROR_CONTAINER_CREATE_FAILED, e.what());
613 LOGT("Adding new container");
615 addContainer(newConfigPath);
616 } catch (SecurityContainersException& e) {
618 removeAllWrapper(containerPathStr);
619 result->setError(api::host::ERROR_CONTAINER_CREATE_FAILED, e.what());
623 auto resultCallback = [result, containerPathStr, removeAllWrapper](bool succeeded) {
627 LOGE("Failed to start container.");
628 removeAllWrapper(containerPathStr);
629 result->setError(api::host::ERROR_CONTAINER_CREATE_FAILED,
630 "Failed to start container.");
633 mContainers[id]->startAsync(resultCallback);
636 } // namespace security_containers