diff --git a/lib/constants.py b/lib/constants.py index 7392a955643f6b5c760b7e5e6c8599d98c3e4896..a6cdcd6b775159ca0841221908538d59a685fb7b 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -496,6 +496,7 @@ HV_SECURITY_MODEL = "security_model" HV_SECURITY_DOMAIN = "security_domain" HV_KVM_FLAG = "kvm_flag" HV_VHOST_NET = "vhost_net" +HV_KVM_USE_CHROOT = "use_chroot" HVS_PARAMETER_TYPES = { HV_BOOT_ORDER: VTYPE_STRING, @@ -527,6 +528,7 @@ HVS_PARAMETER_TYPES = { HV_SECURITY_DOMAIN: VTYPE_STRING, HV_KVM_FLAG: VTYPE_STRING, HV_VHOST_NET: VTYPE_BOOL, + HV_KVM_USE_CHROOT: VTYPE_BOOL, } HVS_PARAMETERS = frozenset(HVS_PARAMETER_TYPES.keys()) @@ -795,6 +797,7 @@ HVC_DEFAULTS = { HV_SECURITY_DOMAIN: '', HV_KVM_FLAG: "", HV_VHOST_NET: False, + HV_KVM_USE_CHROOT: False, }, HT_FAKE: { }, diff --git a/lib/hypervisor/hv_kvm.py b/lib/hypervisor/hv_kvm.py index af04dcacb5e5cae0bcba0b67a324cbc33b7438af..a962a0b6d89bf5952cbb2a8032d92588e2232c9b 100644 --- a/lib/hypervisor/hv_kvm.py +++ b/lib/hypervisor/hv_kvm.py @@ -23,6 +23,7 @@ """ +import errno import os import os.path import re @@ -51,7 +52,16 @@ class KVMHypervisor(hv_base.BaseHypervisor): _UIDS_DIR = _ROOT_DIR + "/uid" # contains instances reserved uids _CTRL_DIR = _ROOT_DIR + "/ctrl" # contains instances control sockets _CONF_DIR = _ROOT_DIR + "/conf" # contains instances startup data - _DIRS = [_ROOT_DIR, _PIDS_DIR, _UIDS_DIR, _CTRL_DIR, _CONF_DIR] + # KVM instances with chroot enabled are started in empty chroot directories. + _CHROOT_DIR = _ROOT_DIR + "/chroot" # for empty chroot directories + # After an instance is stopped, its chroot directory is removed. + # If the chroot directory is not empty, it can't be removed. + # A non-empty chroot directory indicates a possible security incident. + # To support forensics, the non-empty chroot directory is quarantined in + # a separate directory, called 'chroot-quarantine'. + _CHROOT_QUARANTINE_DIR = _ROOT_DIR + "/chroot-quarantine" + _DIRS = [_ROOT_DIR, _PIDS_DIR, _UIDS_DIR, _CTRL_DIR, _CONF_DIR, + _CHROOT_DIR, _CHROOT_QUARANTINE_DIR] PARAMETERS = { constants.HV_KERNEL_PATH: hv_base.OPT_FILE_CHECK, @@ -87,6 +97,7 @@ class KVMHypervisor(hv_base.BaseHypervisor): constants.HV_KVM_FLAG: hv_base.ParamInSet(False, constants.HT_KVM_FLAG_VALUES), constants.HV_VHOST_NET: hv_base.NO_CHECK, + constants.HV_KVM_USE_CHROOT: hv_base.NO_CHECK, } _MIGRATION_STATUS_RE = re.compile('Migration\s+status:\s+(\w+)', @@ -229,6 +240,13 @@ class KVMHypervisor(hv_base.BaseHypervisor): """ return utils.PathJoin(cls._CONF_DIR, "%s.runtime" % instance_name) + @classmethod + def _InstanceChrootDir(cls, instance_name): + """Returns the name of the KVM chroot dir of the instance + + """ + return utils.PathJoin(cls._CHROOT_DIR, instance_name) + @classmethod def _TryReadUidFile(cls, uid_file): """Try to read a uid file @@ -246,7 +264,7 @@ class KVMHypervisor(hv_base.BaseHypervisor): @classmethod def _RemoveInstanceRuntimeFiles(cls, pidfile, instance_name): - """Removes an instance's rutime sockets/files. + """Removes an instance's rutime sockets/files/dirs. """ utils.RemoveFile(pidfile) @@ -258,6 +276,24 @@ class KVMHypervisor(hv_base.BaseHypervisor): utils.RemoveFile(uid_file) if uid is not None: uidpool.ReleaseUid(uid) + try: + chroot_dir = cls._InstanceChrootDir(instance_name) + utils.RemoveDir(chroot_dir) + except OSError, err: + if err.errno == errno.ENOTEMPTY: + # The chroot directory is expected to be empty, but it isn't. + new_chroot_dir = tempfile.mkdtemp(dir=cls._CHROOT_QUARANTINE_DIR, + prefix="%s-%s-" % + (instance_name, + utils.TimestampForFilename())) + logging.warning("The chroot directory of instance %s can not be" + " removed as it is not empty. Moving it to the" + " quarantine instead. Please investigate the" + " contents (%s) and clean up manually", + instance_name, new_chroot_dir) + utils.RenameFile(chroot_dir, new_chroot_dir) + else: + raise def _WriteNetScript(self, instance, seq, nic): """Write a script to connect a net interface to the proper bridge. @@ -529,6 +565,9 @@ class KVMHypervisor(hv_base.BaseHypervisor): if hvp[constants.HV_USE_LOCALTIME]: kvm_cmd.extend(['-localtime']) + if hvp[constants.HV_KVM_USE_CHROOT]: + kvm_cmd.extend(['-chroot', self._InstanceChrootDir(instance.name)]) + # Save the current instance nics, but defer their expansion as parameters, # as we'll need to generate executable temp files for them. kvm_nics = instance.nics @@ -644,6 +683,10 @@ class KVMHypervisor(hv_base.BaseHypervisor): raise errors.HypervisorError("Failed to open VNC password file %s: %s" % (vnc_pwd_file, err)) + if hvp[constants.HV_KVM_USE_CHROOT]: + utils.EnsureDirs([(self._InstanceChrootDir(name), + constants.SECURE_DIR_MODE)]) + if security_model == constants.HT_SM_POOL: ss = ssconf.SimpleStore() uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\n") diff --git a/man/gnt-instance.sgml b/man/gnt-instance.sgml index 2cf24af1459cc4d6c08f3f342982213ef8499d26..3d3563fc4075d20817b5d194d0e9d719a5b3688c 100644 --- a/man/gnt-instance.sgml +++ b/man/gnt-instance.sgml @@ -658,6 +658,25 @@ </listitem> </varlistentry> + <varlistentry> + <term>use_chroot</term> + <listitem> + <simpara>Valid for the KVM hypervisor.</simpara> + + <simpara>This boolean option determines wether to run the KVM + instance in a chroot directory. + </simpara> + <para>If it is set to <quote>true</quote>, an empty directory + is created before starting the instance and its path is passed via + the -chroot flag to kvm. + The directory is removed when the instance is stopped. + </para> + + <simpara>It is set to <quote>false</quote> by default.</simpara> + + </listitem> + </varlistentry> + </variablelist> </para>