From: Lukasz Kostyra Date: Tue, 5 Aug 2014 11:49:56 +0000 (+0200) Subject: Add API to create new containers X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=e1302ae96a14947703e37029398bdb059b4f209f;p=platform%2Fcore%2Fsecurity%2Fvasum.git Add API to create new containers [Feature] Dbus method to add new containers [Cause] Need of dynamic management of containers [Solution] Added dbus API to add new containers. Added new functions to utils needed during dynamic container creation. [Verification] Build, install, run unit tests. Change-Id: I2044c416947dccc3e0e90302f6b56ea49db0baa1 --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 266773a..9cb880a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,10 +69,14 @@ ENDIF(NOT DEFINED LIBVIRT_GROUP) IF(NOT DEFINED INPUT_EVENT_GROUP) SET(INPUT_EVENT_GROUP "input") ENDIF(NOT DEFINED INPUT_EVENT_GROUP) +IF(NOT DEFINED DISK_GROUP) + SET(DISK_GROUP "disk") +ENDIF(NOT DEFINED DISK_GROUP) ADD_DEFINITIONS(-DSECURITY_CONTAINERS_USER="${SECURITY_CONTAINERS_USER}") ADD_DEFINITIONS(-DLIBVIRT_GROUP="${LIBVIRT_GROUP}") ADD_DEFINITIONS(-DINPUT_EVENT_GROUP="${INPUT_EVENT_GROUP}") +ADD_DEFINITIONS(-DDISK_GROUP="${DISK_GROUP}") ## Python packages directory ################################################### diff --git a/common/utils/fs.cpp b/common/utils/fs.cpp index d663831..a03c139 100644 --- a/common/utils/fs.cpp +++ b/common/utils/fs.cpp @@ -39,6 +39,8 @@ #include #include +#include + namespace security_containers { namespace utils { @@ -199,6 +201,63 @@ bool moveFile(const std::string& src, const std::string& dst) return true; } +namespace { + +bool copyDirContentsRec(const boost::filesystem::path& src, const boost::filesystem::path& dst) +{ + namespace fs = boost::filesystem; + + // TODO: Right now this function skips files which produce error when copying. Errors show up + // when: + // a) fs::directory_iterator file(src) is created + // b) fs::copy(...) is called + // In both cases lack of permissions is the issue. + // + // In a) case we can't do much - SCS won't be able to read the directory and its contents. Such + // directories are not common in the filesystem, so they *probably* can be skipped. + // + // In b) case multiple directories have too strict permissions to be directly copied. This + // is a problem for some files crucial to container launch (ex. we cannot copy + // /usr/lib/systemd/systemd because /usr/lib has 555 permissions). + // To fix b) issue, copying must be done in two steps: + // 1. Copy file contents without permissions (this probably can be achieved by opening two + // files in-code with fstream and programatically copying data from one file to another). + // 2. Apply all available file attributes from source (permissions, owner UID/GID, xattrs...) + + try { + for (fs::directory_iterator file(src); + file != fs::directory_iterator(); + ++file) { + fs::path current(file->path()); + + boost::system::error_code ec; + fs::copy(current, dst / current.filename(), ec); + if(ec.value() != boost::system::errc::success) { + LOGW("Failed to copy " << current << ": " << ec.message()); + } + + if (!fs::is_symlink(current) && fs::is_directory(current)) { + if (!copyDirContentsRec(current, dst / current.filename())) { + return false; + } + } + } + } catch (fs::filesystem_error& e) { + LOGW(e.what()); + } + + return true; +} + +} // namespace + +bool copyDirContents(const std::string& src, const std::string& dst) +{ + namespace fs = boost::filesystem; + + return copyDirContentsRec(fs::path(src), fs::path(dst)); +} + bool createDir(const std::string& path, uid_t uid, uid_t gid, boost::filesystem::perms mode) { namespace fs = boost::filesystem; @@ -239,5 +298,35 @@ bool createDir(const std::string& path, uid_t uid, uid_t gid, boost::filesystem: return true; } +bool createEmptyDir(const std::string& path) +{ + namespace fs = boost::filesystem; + + fs::path dirPath(path); + boost::system::error_code ec; + bool cleanDirCreated = false; + + if (!fs::exists(dirPath)) { + if (!fs::create_directory(dirPath, ec)) { + LOGE("Failed to create dir. Error: " << ec.message()); + return false; + } + cleanDirCreated = true; + } else if (!fs::is_directory(dirPath)) { + LOGE("Provided path already exists and is not a dir, cannot create."); + return false; + } + + if (!cleanDirCreated) { + // check if directory is empty if it was already created + if (!fs::is_empty(dirPath)) { + LOGE("Directory has some data inside, cannot be used."); + return false; + } + } + + return true; +} + } // namespace utils } // namespace security_containers diff --git a/common/utils/fs.hpp b/common/utils/fs.hpp index 9cff7a9..0e2f00e 100644 --- a/common/utils/fs.hpp +++ b/common/utils/fs.hpp @@ -87,10 +87,22 @@ bool hasSameMountPoint(const std::string& path1, const std::string& path2, bool& bool moveFile(const std::string& src, const std::string& dst); /** + * Recursively copy contents of src dir to dst dir. + */ +bool copyDirContents(const std::string& src, const std::string& dst); + +/** * Creates a directory with specific UID, GID and permissions set. */ bool createDir(const std::string& path, uid_t uid, uid_t gid, boost::filesystem::perms mode); +/** + * Creates an empty directory, ready to serve as mount point. + * Succeeds either if path did not exist and was created successfully, or if already existing dir + * under the same path is empty and is not a mount point. + */ +bool createEmptyDir(const std::string& path); + } // namespace utils } // namespace security_containers diff --git a/common/utils/img.cpp b/common/utils/img.cpp new file mode 100644 index 0000000..4d7de88 --- /dev/null +++ b/common/utils/img.cpp @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Lukasz Kostyra + * + * Licensed 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 + */ + +/** + * @file img.hpp + * @author Lukasz Kostyra (l.kostyra@samsung.com) + * @brief Image utility functions declaration + */ + +#include "config.hpp" +#include "logger/logger.hpp" +#include "utils/img.hpp" +#include "utils/fs.hpp" +#include "utils/paths.hpp" + +#include +#include +#include + +namespace security_containers { +namespace utils { + +namespace { + +const std::string LOOP_DEV_PREFIX = "/dev/loop"; +const std::string LOOP_MOUNT_POINT_OPTIONS = ""; +const std::string LOOP_MOUNT_POINT_TYPE = "ext4"; +const unsigned long LOOP_MOUNT_POINT_FLAGS = MS_RDONLY; + +// Writes to ret if loop device (provided in loopdev arg) is free to use. +// Returns true if check was successful, false if loop device FD was unavailable for some reason. +bool isLoopDevFree(const std::string& loopdev, bool& ret) +{ + // initialize + ret = false; + + // open loop device FD + int loopFD = ::open(loopdev.c_str(), O_RDWR); + if (loopFD < 0) { + LOGD("Failed to open loop device descriptor: " << ::strerror(errno)); + return false; + } + + // if ioctl with LOOP_GET_STATUS fails, device is not assigned and free to use + struct loop_info linfo; + if (::ioctl(loopFD, LOOP_GET_STATUS, &linfo)) { + ret = true; + } + + ::close(loopFD); + return true; +} + +bool mountLoop(const std::string& img, + const std::string& loopdev, + const std::string& path, + const std::string& type, + unsigned long flags, + const std::string& options) +{ + // to mount an image, we need to connect image FD with loop device FD + // get image file FD + int fileFD = ::open(img.c_str(), O_RDWR); + if (fileFD < 0) { + LOGD("Failed to open image file descriptor: " << ::strerror(errno)); + return false; + } + + // get loop device FD + int loopFD = ::open(loopdev.c_str(), O_RDWR); + if (loopFD < 0) { + LOGD("Failed to open loop device descriptor: " << ::strerror(errno)); + ::close(fileFD); + return false; + } + + // set loop device + if (::ioctl(loopFD, LOOP_SET_FD, fileFD)) { + LOGD("Failed to assign loop device to image: " << ::strerror(errno)); + ::close(fileFD); + ::close(loopFD); + return false; + } + + // mount loop device to path + if (::mount(loopdev.c_str(), path.c_str(), type.c_str(), flags, options.c_str()) != 0) { + LOGD("Mount failed for '" << path << "', options=" << options << ": " << strerror(errno)); + ::ioctl(loopFD, LOOP_CLR_FD, 0); + ::close(fileFD); + ::close(loopFD); + return false; + } + + ::close(fileFD); + ::close(loopFD); + return true; +} + +} // namespace + +// Finds first available loop device and returns its path through ret. +// Returns false if an error occurs, or if all available loop devices are taken. +bool getFreeLoopDevice(std::string& ret) +{ + for (unsigned int i = 0; i < 8; ++i) { + // build path to loop device + const std::string loopdev = LOOP_DEV_PREFIX + std::to_string(i); + bool isFree = false; + + // check if it is free + if (!isLoopDevFree(loopdev, isFree)) { + LOGD("Failed to check status of " << loopdev); + return false; + } + + // if checked loop device is free, we can exit the function and return it + if (isFree) { + ret = loopdev; + return true; + } + } + + LOGD("All loop devices are taken."); + return false; +} + +bool mountImage(const std::string& image, const std::string& path, const std::string& loopdev) +{ + return mountLoop(image, path, loopdev, + LOOP_MOUNT_POINT_TYPE, + LOOP_MOUNT_POINT_FLAGS, + LOOP_MOUNT_POINT_OPTIONS); +} + +bool umountImage(const std::string& path, const std::string& loopdev) +{ + if (::umount(path.c_str()) != 0) { + LOGD("Umount failed for '" << path << "': " << strerror(errno)); + return false; + } + + // clear loop device + int loopFD = ::open(loopdev.c_str(), O_RDWR); + if (loopFD < 0) { + LOGD("Failed to open fd for loop device 0"); + return false; + } + + if (::ioctl(loopFD, LOOP_CLR_FD, 0) < 0) { + LOGD("Failed to clear loop device."); + close(loopFD); + return false; + } + + close(loopFD); + return true; +} + +bool copyImageContents(const std::string& img, const std::string& dst) +{ + namespace fs = boost::filesystem; + boost::system::error_code ec; + + // make sure that image exists + if (!fs::exists(fs::path(img))) { + LOGE("Image " << img << " does not exist"); + return false; + } + + const std::string mountPoint = createFilePath(dirName(img), "/mp/"); + // create a mount point for copied image + if (!createEmptyDir(mountPoint)) { + LOGE("Cannot create mount point for copied image."); + return false; + } + + // create dst directory + if (!createEmptyDir(dst)) { + LOGE("Cannot create directory for data."); + return false; + } + + // find free loop device for image + std::string loopdev; + if (!utils::getFreeLoopDevice(loopdev)) { + LOGE("Failed to get free loop device."); + return false; + } + + LOGT("Using " << loopdev << " to mount image"); + // mount an image + if (!utils::mountImage(img, loopdev, mountPoint)) { + LOGE("Cannot mount image."); + return false; + } + + // copy data + LOGI("Beginning image copy"); + if (!utils::copyDirContents(mountPoint, dst)) { + LOGE("Failed to copy image."); + utils::umountImage(mountPoint, loopdev); + LOGD("Removing already copied data"); + fs::remove_all(fs::path(dst)); + return false; + } + LOGI("Finished image copy"); + + // umount image + if (!utils::umountImage(mountPoint, loopdev)) { + LOGE("Failed to umount image"); + LOGD("Removing copied data"); + fs::remove_all(fs::path(dst)); + return false; + } + + // remove mount point + if (!fs::remove(fs::path(mountPoint), ec)) { + LOGW("Failed to remove mount point: " << ec.message()); + } + + return true; +} + +} // namespace utils +} // namespace security_containers diff --git a/common/utils/img.hpp b/common/utils/img.hpp new file mode 100644 index 0000000..d42300e --- /dev/null +++ b/common/utils/img.hpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Lukasz Kostyra + * + * Licensed 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 + */ + +/** + * @file img.hpp + * @author Lukasz Kostyra (l.kostyra@samsung.com) + * @brief Image utility functions declaration + */ + +#ifndef COMMON_UTILS_IMG_HPP +#define COMMON_UTILS_IMG_HPP + +namespace security_containers { +namespace utils { + +/** + * Returns string with first free loop device. + */ +bool getFreeLoopDevice(std::string& ret); + +/** + * Mount an ext4 image from file on a given path by using a loop device. + */ +bool mountImage(const std::string& image, const std::string& loopdev, const std::string& path); + +/** + * Umounts previously mounted image. + * This call will also free loop device used to mount specified path. + */ +bool umountImage(const std::string& path, const std::string& loopdev); + +/** + * Mounts an image and copies its contents to dst directory. + */ +bool copyImageContents(const std::string& img, const std::string& dst); + +} // namespace utils +} // namespace security_containers + +#endif // COMMON_UTILS_IMG_HPP diff --git a/common/utils/paths.hpp b/common/utils/paths.hpp index e357c71..14132b4 100644 --- a/common/utils/paths.hpp +++ b/common/utils/paths.hpp @@ -107,6 +107,18 @@ inline std::string dirName(std::string path) return path; } +/* + * Gets absolute path to specified file (if needed) + */ +inline std::string getAbsolutePath(const std::string& path, const std::string& base) +{ + if (path[0] == '/') { + return path; + } else { + return utils::createFilePath(base, "/", path); + } +} + } // namespace utils } // namespace security_containers diff --git a/packaging/security-containers.spec b/packaging/security-containers.spec index 30914b0..97bf4d5 100644 --- a/packaging/security-containers.spec +++ b/packaging/security-containers.spec @@ -5,6 +5,8 @@ # The group that has read and write access to /dev/input/event* devices. # It may vary between platforms. %define input_event_group video +# The group has access to /dev/loop* devices. +%define disk_group disk Name: security-containers Version: 0.1.1 @@ -40,9 +42,12 @@ between them. A process from inside a container can request a switch of context %dir /etc/security-containers %dir /etc/security-containers/containers %dir /etc/security-containers/libvirt-config +%dir /etc/security-containers/templates %config /etc/security-containers/daemon.conf %config /etc/security-containers/containers/*.conf %config /etc/security-containers/libvirt-config/*.xml +%config /etc/security-containers/templates/*.conf +%config /etc/security-containers/templates/*.xml %{_unitdir}/security-containers.service %{_unitdir}/multi-user.target.wants/security-containers.service /etc/dbus-1/system.d/org.tizen.containers.host.conf @@ -65,7 +70,8 @@ between them. A process from inside a container can request a switch of context -DPYTHON_SITELIB=%{python_sitelib} \ -DSECURITY_CONTAINERS_USER=%{scs_user} \ -DLIBVIRT_GROUP=%{libvirt_group} \ - -DINPUT_EVENT_GROUP=%{input_event_group} + -DINPUT_EVENT_GROUP=%{input_event_group} \ + -DDISK_GROUP=%{disk_group} make -k %{?jobs:-j%jobs} %install diff --git a/server/configs/CMakeLists.txt b/server/configs/CMakeLists.txt index def06a2..81859c1 100644 --- a/server/configs/CMakeLists.txt +++ b/server/configs/CMakeLists.txt @@ -21,7 +21,7 @@ MESSAGE(STATUS "Installing configs to " ${SC_CONFIG_INSTALL_DIR}) FILE(GLOB container_CONF containers/*.conf) FILE(GLOB admin_CONF libvirt-config/*.xml) - +FILE(GLOB template_CONF templates/*.conf templates/*.xml) ## Generate #################################################################### CONFIGURE_FILE(systemd/security-containers.service.in @@ -45,5 +45,8 @@ INSTALL(FILES ${container_CONF} INSTALL(FILES ${admin_CONF} DESTINATION ${SC_CONFIG_INSTALL_DIR}/libvirt-config) +INSTALL(FILES ${template_CONF} + DESTINATION ${SC_CONFIG_INSTALL_DIR}/templates) + INSTALL(FILES ${CMAKE_BINARY_DIR}/systemd/security-containers.service DESTINATION ${SYSTEMD_UNIT_DIR}) diff --git a/server/configs/daemon.conf b/server/configs/daemon.conf index 67ef356..db85284 100644 --- a/server/configs/daemon.conf +++ b/server/configs/daemon.conf @@ -1,6 +1,9 @@ { "containerConfigs" : ["containers/private.conf", "containers/business.conf" ], "containersPath" : "/opt/usr/containers", + "containerImagePath" : "/opt/usr/containers/img/system-data.img", + "containerTemplatePath" : "templates", + "containerNewConfigPrefix" : "/var/lib/security-containers", "runMountPointPrefix" : "/var/run/containers", "foregroundId" : "private", "defaultId" : "private", diff --git a/server/configs/systemd/security-containers.service.in b/server/configs/systemd/security-containers.service.in index f06dea3..c271341 100644 --- a/server/configs/systemd/security-containers.service.in +++ b/server/configs/systemd/security-containers.service.in @@ -2,6 +2,7 @@ Description=Security Containers Server After=libvirtd.service Requires=libvirtd.service +ConditionVirtualization=no [Service] Type=simple diff --git a/server/configs/templates/template-network.xml b/server/configs/templates/template-network.xml new file mode 100644 index 0000000..f5ec171 --- /dev/null +++ b/server/configs/templates/template-network.xml @@ -0,0 +1,12 @@ + + ~NAME~ + ~UUID~ + + + + + + + + + diff --git a/server/configs/templates/template-nwfilter.xml b/server/configs/templates/template-nwfilter.xml new file mode 100644 index 0000000..82ea8cc --- /dev/null +++ b/server/configs/templates/template-nwfilter.xml @@ -0,0 +1,9 @@ + + ~UUID~ + + + + + + + diff --git a/server/configs/templates/template.conf b/server/configs/templates/template.conf new file mode 100644 index 0000000..17480a0 --- /dev/null +++ b/server/configs/templates/template.conf @@ -0,0 +1,13 @@ +{ + "cpuQuotaForeground" : -1, + "cpuQuotaBackground" : 1000, + "privilege" : 10, + "switchToDefaultAfterTimeout" : true, + "enableDbusIntegration" : true, + "config" : "../libvirt-config/~NAME~.xml", + "networkConfig" : "../libvirt-config/~NAME~-network.xml", + "networkFilterConfig" : "../libvirt-config/~NAME~-nwfilter.xml", + "runMountPoint" : "~NAME~/run", + "permittedToSend" : [ "/tmp/.*" ], + "permittedToRecv" : [ "/tmp/.*" ] +} diff --git a/server/configs/templates/template.xml b/server/configs/templates/template.xml new file mode 100644 index 0000000..bdb452d --- /dev/null +++ b/server/configs/templates/template.xml @@ -0,0 +1,123 @@ + + ~NAME~ + ~UUID~ + 102400 + + exe + /usr/lib/systemd/systemd + + destroy + restart + destroy + + + + + /dev/fb0 + + + + + /dev/tty2 + + + + + /dev/tty3 + + + + + /dev/tty4 + + + + + /dev/tty5 + + + + + /dev/input/event0 + + + + + /dev/input/event1 + + + + + /dev/input/event2 + + + + + /dev/input/event3 + + + + + /dev/input/event4 + + + + + /dev/input/mice + + + + + /dev/input/mouse0 + + + + + + + /dev/log_events + + + + + /dev/log_main + + + + + /dev/log_radio + + + + + /dev/log_system + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/server/container-admin.cpp b/server/container-admin.cpp index 805e9c1..f60d942 100644 --- a/server/container-admin.cpp +++ b/server/container-admin.cpp @@ -90,6 +90,8 @@ ContainerAdmin::ContainerAdmin(const ContainerConfig& config) throw ContainerOperationException(mId + ": Failed to register a libvirt lifecycle callback"); } + LOGT(mId << ": registered lifecycle callback"); + mRebootCallbackId = virConnectDomainEventRegisterAny(virDomainGetConnect(mDom.get()), mDom.get(), VIR_DOMAIN_EVENT_ID_REBOOT, @@ -103,6 +105,8 @@ ContainerAdmin::ContainerAdmin(const ContainerConfig& config) mLifecycleCallbackId); throw ContainerOperationException(mId + ": Failed to register a libvirt reboot callback"); } + + LOGT(mId << ": registered reboot callback"); } diff --git a/server/container.cpp b/server/container.cpp index 60d124e..494b0ae 100644 --- a/server/container.cpp +++ b/server/container.cpp @@ -91,6 +91,10 @@ Container::~Container() if (mReconnectThread.joinable()) { mReconnectThread.join(); } + + if (mStartThread.joinable()) { + mStartThread.join(); + } } const std::vector& Container::getPermittedToSend() const @@ -131,6 +135,30 @@ void Container::start() goBackground(); } +void Container::startAsync(const StartAsyncResultCallback& callback) +{ + if (mStartThread.joinable()) { + mStartThread.join(); + } + + auto startWrapper = [this, callback]() { + bool succeeded = false; + + try { + start(); + succeeded = true; + } catch(std::exception& e) { + LOGE(getId() << ": failed to start: " << e.what()); + } + + if (callback) { + callback(succeeded); + } + }; + + mStartThread = std::thread(startWrapper); +} + void Container::stop() { Lock lock(mReconnectMutex); diff --git a/server/container.hpp b/server/container.hpp index f2d67c0..f741464 100644 --- a/server/container.hpp +++ b/server/container.hpp @@ -56,6 +56,7 @@ public: typedef ContainerConnection::ProxyCallCallback ProxyCallCallback; typedef std::function DbusStateChangedCallback; + typedef std::function StartAsyncResultCallback; /** * Returns a vector of regexps defining files permitted to be @@ -87,6 +88,14 @@ public: void start(); /** + * Boot the container to the background in separate thread. This function immediately exits + * after container booting is started in another thread. + * + * @param callback Called after starting the container. Passes bool with result of starting. + */ + void startAsync(const StartAsyncResultCallback& callback); + + /** * Try to shutdown the container, if failed, destroy it. */ void stop(); @@ -197,6 +206,7 @@ private: std::unique_ptr mAdmin; std::unique_ptr mConnection; std::thread mReconnectThread; + std::thread mStartThread; mutable std::recursive_mutex mReconnectMutex; NotifyActiveContainerCallback mNotifyCallback; DisplayOffCallback mDisplayOffCallback; diff --git a/server/containers-manager-config.hpp b/server/containers-manager-config.hpp index 310dca3..06b83be 100644 --- a/server/containers-manager-config.hpp +++ b/server/containers-manager-config.hpp @@ -62,6 +62,22 @@ struct ContainersManagerConfig { */ std::string containersPath; + /** + * A path where the containers image reside. Empty path means that containers image won't be + * copied to containersPath when creating new container. + */ + std::string containerImagePath; + + /** + * A path where template configuration files for new containers reside + */ + std::string containerTemplatePath; + + /** + * Prefix added to a path for new container configuration files + */ + std::string containerNewConfigPrefix; + /* * Parameters describing input device used to switch between containers */ @@ -83,6 +99,9 @@ struct ContainersManagerConfig { foregroundId, defaultId, containersPath, + containerImagePath, + containerTemplatePath, + containerNewConfigPrefix, inputConfig, runMountPointPrefix, proxyCallRules diff --git a/server/containers-manager.cpp b/server/containers-manager.cpp index 90a6cfc..5dc909b 100644 --- a/server/containers-manager.cpp +++ b/server/containers-manager.cpp @@ -36,9 +36,14 @@ #include "config/manager.hpp" #include "dbus/exception.hpp" #include "utils/fs.hpp" +#include "utils/img.hpp" #include #include +#include +#include +#include +#include #include #include #include @@ -61,13 +66,25 @@ bool regexMatchVector(const std::string& str, const std::vector& v } const std::string HOST_ID = "host"; +const std::string CONTAINER_TEMPLATE_CONFIG_PATH = "template.conf"; +const std::string CONTAINER_TEMPLATE_LIBVIRT_CONFIG_PATH = "template.xml"; +const std::string CONTAINER_TEMPLATE_LIBVIRT_NETWORK_PATH = "template-network.xml"; +const std::string CONTAINER_TEMPLATE_LIBVIRT_NETWORK_FILTER_PATH = "template-nwfilter.xml"; + +const boost::regex CONTAINER_NAME_REGEX("~NAME~"); +const boost::regex CONTAINER_UUID_REGEX("~UUID~"); +const boost::regex CONTAINER_IP_THIRD_OCTET_REGEX("~IP~"); + +const unsigned int CONTAINER_IP_BASE_THIRD_OCTET = 100; } // namespace ContainersManager::ContainersManager(const std::string& managerConfigPath): mDetachOnExit(false) { LOGD("Instantiating ContainersManager object..."); - config::loadFromFile(managerConfigPath, mConfig); + + mConfigPath = managerConfigPath; + config::loadFromFile(mConfigPath, mConfig); mProxyCallPolicy.reset(new ProxyCallPolicy(mConfig.proxyCallRules)); @@ -87,40 +104,11 @@ ContainersManager::ContainersManager(const std::string& managerConfigPath): mDet mHostConnection.setSetActiveContainerCallback(bind(&ContainersManager::handleSetActiveContainerCall, this, _1, _2)); - for (auto& containerConfig : mConfig.containerConfigs) { - std::string containerConfigPath; - - if (containerConfig[0] == '/') { - containerConfigPath = containerConfig; - } else { - std::string baseConfigPath = utils::dirName(managerConfigPath); - containerConfigPath = utils::createFilePath(baseConfigPath, "/", containerConfig); - } - - LOGD("Creating Container " << containerConfigPath); - std::unique_ptr c(new Container(containerConfigPath, - mConfig.runMountPointPrefix)); - const std::string id = c->getId(); - if (id == HOST_ID) { - throw ContainerOperationException("Cannot use reserved container ID"); - } - - c->setNotifyActiveContainerCallback(bind(&ContainersManager::notifyActiveContainerHandler, - this, id, _1, _2)); + mHostConnection.setAddContainerCallback(bind(&ContainersManager::handleAddContainerCall, + this, _1, _2)); - c->setDisplayOffCallback(bind(&ContainersManager::displayOffHandler, - this, id)); - - c->setFileMoveRequestCallback(std::bind(&ContainersManager::handleContainerMoveFileRequest, - this, id, _1, _2, _3)); - - c->setProxyCallCallback(bind(&ContainersManager::handleProxyCall, - this, id, _1, _2, _3, _4, _5, _6, _7)); - - c->setDbusStateChangedCallback(bind(&ContainersManager::handleDbusStateChanged, - this, id, _1)); - - mContainers.insert(ContainerMap::value_type(id, std::move(c))); + for (auto& containerConfig : mConfig.containerConfigs) { + addContainer(containerConfig); } // check if default container exists, throw ContainerOperationException if not found @@ -139,6 +127,8 @@ ContainersManager::ContainersManager(const std::string& managerConfigPath): mDet std::bind(&ContainersManager::switchingSequenceMonitorNotify, this))); } + + } ContainersManager::~ContainersManager() @@ -156,6 +146,38 @@ ContainersManager::~ContainersManager() LOGD("ContainersManager object destroyed"); } +void ContainersManager::addContainer(const std::string& containerConfig) +{ + std::string baseConfigPath = utils::dirName(mConfigPath); + std::string containerConfigPath = utils::getAbsolutePath(containerConfig, baseConfigPath); + + LOGT("Creating Container " << containerConfigPath); + std::unique_ptr c(new Container(containerConfigPath, + mConfig.runMountPointPrefix)); + const std::string id = c->getId(); + if (id == HOST_ID) { + throw ContainerOperationException("Cannot use reserved container ID"); + } + + using namespace std::placeholders; + c->setNotifyActiveContainerCallback(bind(&ContainersManager::notifyActiveContainerHandler, + this, id, _1, _2)); + + c->setDisplayOffCallback(bind(&ContainersManager::displayOffHandler, + this, id)); + + c->setFileMoveRequestCallback(bind(&ContainersManager::handleContainerMoveFileRequest, + this, id, _1, _2, _3)); + + c->setProxyCallCallback(bind(&ContainersManager::handleProxyCall, + this, id, _1, _2, _3, _4, _5, _6, _7)); + + c->setDbusStateChangedCallback(bind(&ContainersManager::handleDbusStateChanged, + this, id, _1)); + + mContainers.insert(ContainerMap::value_type(id, std::move(c))); +} + void ContainersManager::focus(const std::string& containerId) { /* try to access the object first to throw immediately if it doesn't exist */ @@ -465,4 +487,150 @@ void ContainersManager::handleSetActiveContainerCall(const std::string& id, result->setVoid(); } + +void ContainersManager::generateNewConfig(const std::string& id, + const std::string& templatePath, + const std::string& resultPath) +{ + namespace fs = boost::filesystem; + + std::string resultFileDir = utils::dirName(resultPath); + if (!fs::exists(resultFileDir)) { + if (!utils::createEmptyDir(resultFileDir)) { + LOGE("Unable to create directory for new config."); + throw ContainerOperationException("Unable to create directory for new config."); + } + } + + fs::path resultFile(resultPath); + if (fs::exists(resultFile)) { + LOGT(resultPath << " already exists, removing"); + fs::remove(resultFile); + } + + std::string config; + if (!utils::readFileContent(templatePath, config)) { + LOGE("Failed to read template config file."); + throw ContainerOperationException("Failed to read template config file."); + } + + std::string resultConfig = boost::regex_replace(config, CONTAINER_NAME_REGEX, id); + + boost::uuids::uuid u = boost::uuids::random_generator()(); + std::string uuidStr = to_string(u); + LOGD("uuid: " << uuidStr); + resultConfig = boost::regex_replace(resultConfig, CONTAINER_UUID_REGEX, uuidStr); + + // generate third IP octet for network config + std::string thirdOctetStr = std::to_string(CONTAINER_IP_BASE_THIRD_OCTET + mContainers.size() + 1); + LOGD("ip_third_octet: " << thirdOctetStr); + resultConfig = boost::regex_replace(resultConfig, CONTAINER_IP_THIRD_OCTET_REGEX, thirdOctetStr); + + if (!utils::saveFileContent(resultPath, resultConfig)) { + LOGE("Faield to save new config file."); + throw ContainerOperationException("Failed to save new config file."); + } + + // restrict new config file so that only owner (security-containers) can write it + fs::permissions(resultPath, fs::perms::owner_all | + fs::perms::group_read | + fs::perms::others_read); +} + +void ContainersManager::handleAddContainerCall(const std::string& id, + dbus::MethodResultBuilder::Pointer result) +{ + LOGI("Adding container " << id); + + // TODO: This solution is temporary. It utilizes direct access to config files when creating new + // containers. Update this handler when config database will appear. + namespace fs = boost::filesystem; + + boost::system::error_code ec; + const std::string containerPathStr = utils::createFilePath(mConfig.containersPath, "/", id, "/"); + + // check if container does not exist + if (mContainers.find(id) != mContainers.end()) { + LOGE("Cannot create " << id << " container - already exists!"); + result->setError(api::host::ERROR_CONTAINER_CREATE_FAILED, + "Cannot create " + id + " container - already exists!"); + return; + } + + // copy container image if config contains path to image + LOGT("image path: " << mConfig.containerImagePath); + if (!mConfig.containerImagePath.empty()) { + if (!utils::copyImageContents(mConfig.containerImagePath, containerPathStr)) { + LOGE("Failed to copy container image."); + result->setError(api::host::ERROR_CONTAINER_CREATE_FAILED, + "Failed to copy container image."); + return; + } + } + + // generate paths to new configuration files + std::string baseDir = utils::dirName(mConfigPath); + std::string configDir = utils::getAbsolutePath(mConfig.containerNewConfigPrefix, baseDir); + std::string templateDir = utils::getAbsolutePath(mConfig.containerTemplatePath, baseDir); + + std::string configPath = utils::createFilePath(templateDir, "/", CONTAINER_TEMPLATE_CONFIG_PATH); + std::string newConfigPath = utils::createFilePath(configDir, "/containers/", id + ".conf"); + std::string libvirtConfigPath = utils::createFilePath(templateDir, "/", CONTAINER_TEMPLATE_LIBVIRT_CONFIG_PATH); + std::string newLibvirtConfigPath = utils::createFilePath(configDir, "/libvirt-config/", id + ".xml"); + std::string libvirtNetworkPath = utils::createFilePath(templateDir, "/", CONTAINER_TEMPLATE_LIBVIRT_NETWORK_PATH); + std::string newLibvirtNetworkPath = utils::createFilePath(configDir, "/libvirt-config/", id + "-network.xml"); + std::string libvirtNetworkFilterPath = utils::createFilePath(templateDir, "/", CONTAINER_TEMPLATE_LIBVIRT_NETWORK_FILTER_PATH); + std::string newLibvirtNetworkFilterPath = utils::createFilePath(configDir, "/libvirt-config/", id + "-nwfilter.xml"); + + auto removeAllWrapper = [](const std::string& path) { + try { + LOGD("Removing copied data"); + fs::remove_all(fs::path(path)); + } catch(const boost::exception& e) { + LOGW("Failed to remove data: " << boost::diagnostic_information(e)); + } + }; + + try { + LOGI("Generating config from " << configPath << " to " << newConfigPath); + generateNewConfig(id, configPath, newConfigPath); + + LOGI("Generating config from " << libvirtConfigPath << " to " << newLibvirtConfigPath); + generateNewConfig(id, libvirtConfigPath, newLibvirtConfigPath); + + LOGI("Generating config from " << libvirtNetworkPath << " to " << newLibvirtNetworkPath); + generateNewConfig(id, libvirtNetworkPath, newLibvirtNetworkPath); + + LOGI("Generating config from " << libvirtNetworkFilterPath << " to " << newLibvirtNetworkFilterPath); + generateNewConfig(id, libvirtNetworkFilterPath, newLibvirtNetworkFilterPath); + } catch (SecurityContainersException& e) { + LOGE(e.what()); + removeAllWrapper(containerPathStr); + result->setError(api::host::ERROR_CONTAINER_CREATE_FAILED, e.what()); + return; + } + + LOGT("Adding new container"); + try { + addContainer(newConfigPath); + } catch (SecurityContainersException& e) { + LOGE(e.what()); + removeAllWrapper(containerPathStr); + result->setError(api::host::ERROR_CONTAINER_CREATE_FAILED, e.what()); + return; + } + + auto resultCallback = [result, containerPathStr, removeAllWrapper](bool succeeded) { + if (succeeded) { + result->setVoid(); + } else { + LOGE("Failed to start container."); + removeAllWrapper(containerPathStr); + result->setError(api::host::ERROR_CONTAINER_CREATE_FAILED, + "Failed to start container."); + } + }; + mContainers[id]->startAsync(resultCallback); +} + } // namespace security_containers diff --git a/server/containers-manager.hpp b/server/containers-manager.hpp index 33767a9..cd1194d 100644 --- a/server/containers-manager.hpp +++ b/server/containers-manager.hpp @@ -48,6 +48,13 @@ public: ~ContainersManager(); /** + * Add new container. + * + * @param containerConfig config of new container + */ + void addContainer(const std::string& containerConfig); + + /** * Focus this container, put it to the foreground. * Method blocks until the focus is switched. * @@ -77,6 +84,7 @@ public: private: ContainersManagerConfig mConfig; + std::string mConfigPath; HostConnection mHostConnection; // to hold InputMonitor pointer to monitor if container switching sequence is recognized std::unique_ptr mSwitchingSequenceMonitor; @@ -86,6 +94,10 @@ private: bool mDetachOnExit; void switchingSequenceMonitorNotify(); + void generateNewConfig(const std::string& id, + const std::string& templatePath, + const std::string& resultPath); + void notifyActiveContainerHandler(const std::string& caller, const std::string& appliaction, const std::string& message); @@ -108,7 +120,8 @@ private: void handleGetActiveContainerIdCall(dbus::MethodResultBuilder::Pointer result); void handleSetActiveContainerCall(const std::string& id, dbus::MethodResultBuilder::Pointer result); - + void handleAddContainerCall(const std::string& id, + dbus::MethodResultBuilder::Pointer result); }; diff --git a/server/host-connection.cpp b/server/host-connection.cpp index ae32de2..d25bee7 100644 --- a/server/host-connection.cpp +++ b/server/host-connection.cpp @@ -130,6 +130,11 @@ void HostConnection::setSetActiveContainerCallback(const SetActiveContainerCallb mSetActiveContainerCallback = callback; } +void HostConnection::setAddContainerCallback(const AddContainerCallback& callback) +{ + mAddContainerCallback = callback; +} + void HostConnection::onMessageCall(const std::string& objectPath, const std::string& interface, const std::string& methodName, @@ -199,6 +204,15 @@ void HostConnection::onMessageCall(const std::string& objectPath, } return; } + + if (methodName == api::host::METHOD_ADD_CONTAINER) { + const gchar* id = NULL; + g_variant_get(parameters, "(&s)", &id); + + if (mAddContainerCallback){ + mAddContainerCallback(id, result); + } + } } void HostConnection::proxyCallAsync(const std::string& busName, diff --git a/server/host-connection.hpp b/server/host-connection.hpp index bc9015d..c5d1bcc 100644 --- a/server/host-connection.hpp +++ b/server/host-connection.hpp @@ -60,6 +60,9 @@ public: typedef std::function SetActiveContainerCallback; + typedef std::function AddContainerCallback; /** * Register proxy call callback @@ -92,6 +95,11 @@ public: void setSetActiveContainerCallback(const SetActiveContainerCallback& callback); /** + * Register a callback called to create new container + */ + void setAddContainerCallback(const AddContainerCallback& callback); + + /** * Make a proxy call */ void proxyCallAsync(const std::string& busName, @@ -112,6 +120,7 @@ private: GetContainerIdsCallback mGetContainerIdsCallback; GetActiveContainerIdCallback mGetActiveContainerIdCallback; SetActiveContainerCallback mSetActiveContainerCallback; + AddContainerCallback mAddContainerCallback; void onNameAcquired(); void onNameLost(); diff --git a/server/host-dbus-definitions.hpp b/server/host-dbus-definitions.hpp index 12e8d4a..040b10d 100644 --- a/server/host-dbus-definitions.hpp +++ b/server/host-dbus-definitions.hpp @@ -37,11 +37,13 @@ const std::string OBJECT_PATH = "/org/tizen/containers/host"; const std::string INTERFACE = "org.tizen.containers.host.manager"; const std::string ERROR_CONTAINER_STOPPED = "org.tizen.containers.host.Error.ContainersStopped"; +const std::string ERROR_CONTAINER_CREATE_FAILED = "org.tizen.containers.host.Error.ContainerCreateFailed"; const std::string METHOD_GET_CONTAINER_DBUSES = "GetContainerDbuses"; const std::string METHOD_GET_CONTAINER_ID_LIST = "GetContainerIds"; const std::string METHOD_GET_ACTIVE_CONTAINER_ID = "GetActiveContainerId"; const std::string METHOD_SET_ACTIVE_CONTAINER = "SetActiveContainer"; +const std::string METHOD_ADD_CONTAINER = "AddContainer"; const std::string SIGNAL_CONTAINER_DBUS_STATE = "ContainerDbusState"; @@ -70,6 +72,9 @@ const std::string DEFINITION = " " " " " " + " " + " " + " " " " " " " " diff --git a/server/server.cpp b/server/server.cpp index 5c3420d..547d023 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -58,6 +58,10 @@ #error "LIBVIRT_GROUP must be defined!" #endif +#ifndef DISK_GROUP +#error "DISK_GROUP must be defined!" +#endif + extern char** environ; namespace security_containers { @@ -165,6 +169,16 @@ bool Server::prepareEnvironment(const std::string& configPath, bool runAsRoot) } } + // create directory for additional container data (if needed) + if (!config.containerNewConfigPrefix.empty()) { + if (!utils::createDir(config.containerNewConfigPrefix, uid, gid, + fs::perms::owner_all | + fs::perms::group_read | fs::perms::group_exe | + fs::perms::others_read | fs::perms::others_exe)) { + return false; + } + } + // Omit supplementaty group setup and root drop if the user is already switched. // This situation will happen during daemon update triggered by SIGUSR1. if (!runAsRoot && geteuid() == uid) { @@ -173,7 +187,9 @@ bool Server::prepareEnvironment(const std::string& configPath, bool runAsRoot) // LIBVIRT_GROUP provides access to libvirt's daemon socket. // INPUT_EVENT_GROUP provides access to /dev/input/event* devices used by InputMonitor. - if (!utils::setSuppGroups({LIBVIRT_GROUP, INPUT_EVENT_GROUP})) { + // DISK_GROUP provides access to /dev/loop* devices, needed when adding new container to copy + // containers image + if (!utils::setSuppGroups({LIBVIRT_GROUP, INPUT_EVENT_GROUP, DISK_GROUP})) { return false; } diff --git a/tests/unit_tests/client/configs/ut-client/test-dbus-daemon.conf b/tests/unit_tests/client/configs/ut-client/test-dbus-daemon.conf index 2a6ad35..707be02 100644 --- a/tests/unit_tests/client/configs/ut-client/test-dbus-daemon.conf +++ b/tests/unit_tests/client/configs/ut-client/test-dbus-daemon.conf @@ -5,6 +5,9 @@ "foregroundId" : "ut-containers-manager-console1-dbus", "defaultId" : "ut-containers-manager-console1-dbus", "containersPath" : "/tmp", + "containerImagePath" : "", + "containerTemplatePath" : "", + "containerNewConfigPrefix" : "", "runMountPointPrefix" : "", "inputConfig" : {"enabled" : false, "device" : "/dev/doesnotexist", diff --git a/tests/unit_tests/server/configs/CMakeLists.txt b/tests/unit_tests/server/configs/CMakeLists.txt index 816a5bc..ee1c7d9 100644 --- a/tests/unit_tests/server/configs/CMakeLists.txt +++ b/tests/unit_tests/server/configs/CMakeLists.txt @@ -26,6 +26,8 @@ FILE(GLOB server_admin_CONF ut-server/libvirt-config/*.xml) FILE(GLOB manager_manager_CONF ut-containers-manager/*.conf) FILE(GLOB manager_container_CONF ut-containers-manager/containers/*.conf) FILE(GLOB manager_admin_CONF ut-containers-manager/libvirt-config/*.xml) +FILE(GLOB manager_admin_TEMPLATE ut-containers-manager/templates/*.conf + ut-containers-manager/templates/*.xml) FILE(GLOB container_CONF ut-container/*.conf) FILE(GLOB container_container_CONF ut-container/containers/*.conf) @@ -61,7 +63,10 @@ CONFIGURE_FILE(ut-containers-manager/libvirt-config/console2-dbus.xml.in ${CMAKE_BINARY_DIR}/ut-containers-manager/libvirt-config/console2-dbus.xml @ONLY) CONFIGURE_FILE(ut-containers-manager/libvirt-config/console3-dbus.xml.in ${CMAKE_BINARY_DIR}/ut-containers-manager/libvirt-config/console3-dbus.xml @ONLY) +CONFIGURE_FILE(ut-containers-manager/templates/template.xml.in + ${CMAKE_BINARY_DIR}/ut-containers-manager/templates/template.xml @ONLY) FILE(GLOB manager_admin_CONF_GEN ${CMAKE_BINARY_DIR}/ut-containers-manager/libvirt-config/*.xml) +FILE(GLOB manager_admin_TEMPLATE_GEN ${CMAKE_BINARY_DIR}/ut-containers-manager/templates/*.xml) CONFIGURE_FILE(ut-container/libvirt-config/test-dbus.xml.in ${CMAKE_BINARY_DIR}/ut-container/libvirt-config/test-dbus.xml @ONLY) @@ -84,6 +89,10 @@ INSTALL(FILES ${manager_admin_CONF} DESTINATION ${SC_TEST_CONFIG_INSTALL_DIR}/server/ut-containers-manager/libvirt-config) INSTALL(FILES ${manager_admin_CONF_GEN} DESTINATION ${SC_TEST_CONFIG_INSTALL_DIR}/server/ut-containers-manager/libvirt-config) +INSTALL(FILES ${manager_admin_TEMPLATE} + DESTINATION ${SC_TEST_CONFIG_INSTALL_DIR}/server/ut-containers-manager/templates) +INSTALL(FILES ${manager_admin_TEMPLATE_GEN} + DESTINATION ${SC_TEST_CONFIG_INSTALL_DIR}/server/ut-containers-manager/templates) INSTALL(FILES ${container_CONF} DESTINATION ${SC_TEST_CONFIG_INSTALL_DIR}/server/ut-container) diff --git a/tests/unit_tests/server/configs/ut-containers-manager/buggy-daemon.conf b/tests/unit_tests/server/configs/ut-containers-manager/buggy-daemon.conf index 92abbe2..5ec98cc 100644 --- a/tests/unit_tests/server/configs/ut-containers-manager/buggy-daemon.conf +++ b/tests/unit_tests/server/configs/ut-containers-manager/buggy-daemon.conf @@ -4,6 +4,9 @@ "foregroundId" : "ut-containers-manager-console1", "defaultId" : "ut-containers-manager-console1", "containersPath" : "/tmp", + "containerImagePath" : "", + "containerTemplatePath" : "templates", + "containerNewConfigPrefix" : "/usr/share/security-containers/tests/server/ut-containers-manager/", "inputConfig" : {"enabled" : false, "device" : "/dev/doesnotexist", "code" : 139, diff --git a/tests/unit_tests/server/configs/ut-containers-manager/buggy-default-daemon.conf b/tests/unit_tests/server/configs/ut-containers-manager/buggy-default-daemon.conf index a19268c..ef5a597 100644 --- a/tests/unit_tests/server/configs/ut-containers-manager/buggy-default-daemon.conf +++ b/tests/unit_tests/server/configs/ut-containers-manager/buggy-default-daemon.conf @@ -4,6 +4,9 @@ "foregroundId" : "ut-containers-manager-console1", "defaultId" : "in_no_way_there_is_a_valid_id_here", "containersPath" : "/tmp", + "containerImagePath" : "", + "containerTemplatePath" : "templates", + "containerNewConfigPrefix" : "/usr/share/security-containers/tests/server/ut-containers-manager/", "inputConfig" : {"enabled" : false, "device" : "/dev/doesnotexist", "code" : 139, diff --git a/tests/unit_tests/server/configs/ut-containers-manager/buggy-foreground-daemon.conf b/tests/unit_tests/server/configs/ut-containers-manager/buggy-foreground-daemon.conf index bcaba00..3faa0f7 100644 --- a/tests/unit_tests/server/configs/ut-containers-manager/buggy-foreground-daemon.conf +++ b/tests/unit_tests/server/configs/ut-containers-manager/buggy-foreground-daemon.conf @@ -4,6 +4,9 @@ "foregroundId" : "this_id_does_not_exist", "defaultId" : "ut-containers-manager-console1", "containersPath" : "/tmp", + "containerImagePath" : "", + "containerTemplatePath" : "templates", + "containerNewConfigPrefix" : "/usr/share/security-containers/tests/server/ut-containers-manager/", "inputConfig" : {"enabled" : false, "device" : "/dev/doesnotexist", "code" : 139, diff --git a/tests/unit_tests/server/configs/ut-containers-manager/templates/template-network.xml b/tests/unit_tests/server/configs/ut-containers-manager/templates/template-network.xml new file mode 100644 index 0000000..b357c0e --- /dev/null +++ b/tests/unit_tests/server/configs/ut-containers-manager/templates/template-network.xml @@ -0,0 +1,4 @@ + + ~NAME~-network + ~UUID~ + diff --git a/tests/unit_tests/server/configs/ut-containers-manager/templates/template-nwfilter.xml b/tests/unit_tests/server/configs/ut-containers-manager/templates/template-nwfilter.xml new file mode 100644 index 0000000..b96197b --- /dev/null +++ b/tests/unit_tests/server/configs/ut-containers-manager/templates/template-nwfilter.xml @@ -0,0 +1,3 @@ + + ~UUID~ + diff --git a/tests/unit_tests/server/configs/ut-containers-manager/templates/template.conf b/tests/unit_tests/server/configs/ut-containers-manager/templates/template.conf new file mode 100644 index 0000000..40dcacc --- /dev/null +++ b/tests/unit_tests/server/configs/ut-containers-manager/templates/template.conf @@ -0,0 +1,13 @@ +{ + "privilege" : 20, + "switchToDefaultAfterTimeout" : true, + "config" : "../libvirt-config/~NAME~.xml", + "networkConfig" : "../libvirt-config/~NAME~-network.xml", + "networkFilterConfig" : "../libvirt-config/~NAME~-nwfilter.xml", + "cpuQuotaForeground" : -1, + "cpuQuotaBackground" : 1000, + "runMountPoint" : "/tmp/ut-containers-manager/~NAME~-dbus", + "enableDbusIntegration" : true, + "permittedToSend" : [ "/tmp/.*" ], + "permittedToRecv" : [ "/tmp/.*" ] +} diff --git a/tests/unit_tests/server/configs/ut-containers-manager/templates/template.xml.in b/tests/unit_tests/server/configs/ut-containers-manager/templates/template.xml.in new file mode 100644 index 0000000..fbb12a5 --- /dev/null +++ b/tests/unit_tests/server/configs/ut-containers-manager/templates/template.xml.in @@ -0,0 +1,15 @@ + + ~NAME~ + ~UUID~ + 102400 + + exe + /usr/bin/dbus-daemon + --nofork + --config-file=@SC_TEST_CONFIG_INSTALL_DIR@/server/ut-containers-manager/ut-dbus.conf + --address=unix:path=/tmp/ut-containers-manager/~NAME~-dbus/dbus/system_bus_socket + + + + + diff --git a/tests/unit_tests/server/configs/ut-containers-manager/test-daemon.conf b/tests/unit_tests/server/configs/ut-containers-manager/test-daemon.conf index 6147c03..9317b9d 100644 --- a/tests/unit_tests/server/configs/ut-containers-manager/test-daemon.conf +++ b/tests/unit_tests/server/configs/ut-containers-manager/test-daemon.conf @@ -4,6 +4,9 @@ "foregroundId" : "ut-containers-manager-console1", "defaultId" : "ut-containers-manager-console1", "containersPath" : "/tmp", + "containerImagePath" : "", + "containerTemplatePath" : "templates", + "containerNewConfigPrefix" : "/usr/share/security-containers/tests/server/ut-containers-manager/", "inputConfig" : {"enabled" : false, "device" : "/dev/doesnotexist", "code" : 139, diff --git a/tests/unit_tests/server/configs/ut-containers-manager/test-dbus-daemon.conf b/tests/unit_tests/server/configs/ut-containers-manager/test-dbus-daemon.conf index 2a6ad35..af183a1 100644 --- a/tests/unit_tests/server/configs/ut-containers-manager/test-dbus-daemon.conf +++ b/tests/unit_tests/server/configs/ut-containers-manager/test-dbus-daemon.conf @@ -5,6 +5,9 @@ "foregroundId" : "ut-containers-manager-console1-dbus", "defaultId" : "ut-containers-manager-console1-dbus", "containersPath" : "/tmp", + "containerImagePath" : "", + "containerTemplatePath" : "templates", + "containerNewConfigPrefix" : "/usr/share/security-containers/tests/server/ut-containers-manager/", "runMountPointPrefix" : "", "inputConfig" : {"enabled" : false, "device" : "/dev/doesnotexist", diff --git a/tests/unit_tests/server/configs/ut-server/buggy-daemon.conf b/tests/unit_tests/server/configs/ut-server/buggy-daemon.conf index cad519c..4758406 100644 --- a/tests/unit_tests/server/configs/ut-server/buggy-daemon.conf +++ b/tests/unit_tests/server/configs/ut-server/buggy-daemon.conf @@ -1,9 +1,12 @@ { "containerConfigs" : ["containers/container1.conf", "missing/file/path/missing.conf", "containers/container3.conf"], "containersPath" : "/tmp", + "containerImagePath" : "", + "containerTemplatePath" : "no_need_for_templates_in_this_test", "runMountPointPrefix" : "", "foregroundId" : "ut-server-container1", "defaultId" : "ut-server-container1", + "containerNewConfigPrefix" : "", "inputConfig" : {"enabled" : false, "device" : "/dev/doesnotexist", "code" : 139, diff --git a/tests/unit_tests/server/configs/ut-server/test-daemon.conf b/tests/unit_tests/server/configs/ut-server/test-daemon.conf index 868f12b..767b3a5 100644 --- a/tests/unit_tests/server/configs/ut-server/test-daemon.conf +++ b/tests/unit_tests/server/configs/ut-server/test-daemon.conf @@ -1,6 +1,9 @@ { "containerConfigs" : ["containers/container1.conf", "containers/container2.conf", "containers/container3.conf"], "containersPath" : "/tmp", + "containerImagePath" : "", + "containerTemplatePath" : "no_need_for_templates_in_this_test", + "containerNewConfigPrefix" : "", "runMountPointPrefix" : "", "foregroundId" : "ut-server-container1", "defaultId" : "ut-server-container1", diff --git a/tests/unit_tests/server/ut-containers-manager.cpp b/tests/unit_tests/server/ut-containers-manager.cpp index 54a34bb..528d744 100644 --- a/tests/unit_tests/server/ut-containers-manager.cpp +++ b/tests/unit_tests/server/ut-containers-manager.cpp @@ -40,6 +40,7 @@ #include "config/exception.hpp" #include "utils/latch.hpp" #include "utils/fs.hpp" +#include "utils/img.hpp" #include #include @@ -63,6 +64,8 @@ const std::string TEST_DBUS_CONFIG_PATH = SC_TEST_CONFIG_INSTALL_DIR "/server/ut const std::string BUGGY_CONFIG_PATH = SC_TEST_CONFIG_INSTALL_DIR "/server/ut-containers-manager/buggy-daemon.conf"; const std::string BUGGY_FOREGROUND_CONFIG_PATH = SC_TEST_CONFIG_INSTALL_DIR "/server/ut-containers-manager/buggy-foreground-daemon.conf"; const std::string BUGGY_DEFAULTID_CONFIG_PATH = SC_TEST_CONFIG_INSTALL_DIR "/server/ut-containers-manager/buggy-default-daemon.conf"; +const std::string TEST_CONTAINER_CONF_PATH = SC_TEST_CONFIG_INSTALL_DIR "/server/ut-containers-manager/containers/"; +const std::string TEST_CONTAINER_LIBVIRT_CONF_PATH = SC_TEST_CONFIG_INSTALL_DIR "/server/ut-containers-manager/libvirt-config/"; const std::string MISSING_CONFIG_PATH = "/this/is/a/missing/file/path/missing-daemon.conf"; const int EVENT_TIMEOUT = 5000; const int TEST_DBUS_CONNECTION_CONTAINERS_COUNT = 3; @@ -81,6 +84,7 @@ public: typedef std::function TestApiMethodCallback; + typedef std::function AddContainerResultCallback; typedef std::map Dbuses; @@ -303,6 +307,25 @@ public: } + void callAsyncMethodAddContainer(const std::string& id, + const AddContainerResultCallback& result) + { + auto asyncResult = [result](dbus::AsyncMethodCallResult& asyncMethodCallResult) { + BOOST_CHECK(g_variant_is_of_type(asyncMethodCallResult.get(), G_VARIANT_TYPE_UNIT)); + result(); + }; + + assert(isHost()); + GVariant* parameters = g_variant_new("(s)", id.c_str()); + mClient->callMethodAsync(api::host::BUS_NAME, + api::host::OBJECT_PATH, + api::host::INTERFACE, + api::host::METHOD_ADD_CONTAINER, + parameters, + "()", + asyncResult); + } + private: const int mId; DbusConnection::Pointer mClient; @@ -331,6 +354,27 @@ std::function expectedMessage(const std::string& me }; } +class FileCleanerRAII { +public: + FileCleanerRAII(const std::vector& filePathsToClean): + mFilePathsToClean(filePathsToClean) + { } + + ~FileCleanerRAII() + { + namespace fs = boost::filesystem; + for (const auto& file : mFilePathsToClean) { + fs::path f(file); + if (fs::exists(f)) { + fs::remove(f); + } + } + } + +private: + const std::vector mFilePathsToClean; +}; + struct Fixture { security_containers::utils::ScopedGlibLoop mLoop; }; @@ -881,4 +925,34 @@ BOOST_AUTO_TEST_CASE(SetActiveContainerTest) DbusException); } +BOOST_AUTO_TEST_CASE(AddContainerTest) +{ + const std::string newContainerId = "test1234"; + const std::vector newContainerConfigs = { + TEST_CONTAINER_CONF_PATH + newContainerId + ".conf", + TEST_CONTAINER_LIBVIRT_CONF_PATH + newContainerId + ".xml", + TEST_CONTAINER_LIBVIRT_CONF_PATH + newContainerId + "-network.xml", + TEST_CONTAINER_LIBVIRT_CONF_PATH + newContainerId + "-nwfilter.xml", + }; + FileCleanerRAII cleaner(newContainerConfigs); + + ContainersManager cm(TEST_DBUS_CONFIG_PATH); + cm.startAll(); + + Latch callDone; + auto resultCallback = [&]() { + callDone.set(); + }; + + DbusAccessory dbus(DbusAccessory::HOST_ID); + + // create new container + dbus.callAsyncMethodAddContainer(newContainerId, resultCallback); + callDone.wait(EVENT_TIMEOUT); + + // focus new container + BOOST_REQUIRE_NO_THROW(cm.focus(newContainerId)); + BOOST_CHECK(cm.getRunningForegroundContainerId() == newContainerId); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/unit_tests/utils/ut-fs.cpp b/tests/unit_tests/utils/ut-fs.cpp index 8f7c965..43be58a 100644 --- a/tests/unit_tests/utils/ut-fs.cpp +++ b/tests/unit_tests/utils/ut-fs.cpp @@ -44,6 +44,12 @@ const std::string FILE_PATH = SC_TEST_CONFIG_INSTALL_DIR "/utils/ut-fs/file.txt" const std::string FILE_CONTENT = "File content\n" "Line 1\n" "Line 2\n"; +const std::string FILE_CONTENT_2 = "Some other content\n" + "Just to see if\n" + "everything is copied correctly\n"; +const std::string FILE_CONTENT_3 = "More content\n" + "More and more content\n" + "That's a lot of data to test\n"; const std::string BUGGY_FILE_PATH = "/some/missing/file/path/file.txt"; const std::string TMP_PATH = "/tmp"; const std::string FILE_PATH_RANDOM = @@ -52,6 +58,12 @@ const std::string MOUNT_POINT_RANDOM_1 = boost::filesystem::unique_path("/tmp/mountPoint-%%%%").string(); const std::string MOUNT_POINT_RANDOM_2 = boost::filesystem::unique_path("/tmp/mountPoint-%%%%").string(); +const std::string FILE_DIR_RANDOM_1 = + boost::filesystem::unique_path("testDir-%%%%").string(); +const std::string FILE_DIR_RANDOM_2 = + boost::filesystem::unique_path("testDir-%%%%").string(); +const std::string FILE_DIR_RANDOM_3 = + boost::filesystem::unique_path("testDir-%%%%").string(); const std::string FILE_NAME_RANDOM_1 = boost::filesystem::unique_path("testFile-%%%%").string(); const std::string FILE_NAME_RANDOM_2 = @@ -131,4 +143,43 @@ BOOST_AUTO_TEST_CASE(MoveFileTest) BOOST_REQUIRE(fs::remove(MOUNT_POINT_RANDOM_2, ec)); } +BOOST_AUTO_TEST_CASE(CopyDirContentsTest) +{ + namespace fs = boost::filesystem; + std::string src, src_inner, dst, dst_inner; + boost::system::error_code ec; + + src = TMP_PATH + "/" + FILE_DIR_RANDOM_1; + src_inner = src + "/" + FILE_DIR_RANDOM_3; + + dst = TMP_PATH + "/" + FILE_DIR_RANDOM_2; + dst_inner = dst + "/" + FILE_DIR_RANDOM_3; + + // create entire structure with files + BOOST_REQUIRE(fs::create_directory(src, ec)); + BOOST_REQUIRE(ec.value() == 0); + BOOST_REQUIRE(fs::create_directory(src_inner, ec)); + BOOST_REQUIRE(ec.value() == 0); + + BOOST_REQUIRE(saveFileContent(src + "/" + FILE_NAME_RANDOM_1, FILE_CONTENT)); + BOOST_REQUIRE(saveFileContent(src + "/" + FILE_NAME_RANDOM_2, FILE_CONTENT_2)); + BOOST_REQUIRE(saveFileContent(src_inner + "/" + FILE_NAME_RANDOM_1, FILE_CONTENT_3)); + + BOOST_REQUIRE(fs::create_directory(dst, ec)); + BOOST_REQUIRE(ec.value() == 0); + + // copy data + BOOST_CHECK(copyDirContents(src, dst)); + + // check if copy is successful + BOOST_CHECK(fs::exists(dst + "/" + FILE_NAME_RANDOM_1)); + BOOST_CHECK(fs::exists(dst + "/" + FILE_NAME_RANDOM_2)); + BOOST_CHECK(fs::exists(dst_inner)); + BOOST_CHECK(fs::exists(dst_inner + "/" + FILE_NAME_RANDOM_1)); + + BOOST_CHECK_EQUAL(readFileContent(dst + "/" + FILE_NAME_RANDOM_1), FILE_CONTENT); + BOOST_CHECK_EQUAL(readFileContent(dst + "/" + FILE_NAME_RANDOM_2), FILE_CONTENT_2); + BOOST_CHECK_EQUAL(readFileContent(dst_inner + "/" + FILE_NAME_RANDOM_1), FILE_CONTENT_3); +} + BOOST_AUTO_TEST_SUITE_END()