Commit 547a63b7 authored by Balazs Lecz's avatar Balazs Lecz
Browse files

Add KVM chroot feature

This patch adds a new boolean hypervisor parameter to the KVM hypervisor,
named 'use_chroot'.
If it's turned on for an instance, than KVM is started in "chroot mode":
Ganeti creates an empty directory for the instance and passes the path
of this dir to KVM via the -chroot flag.
KVM changes its root to this directory after starting up.

It also adds a "quarantine" feature for moving any unexpected files to
a separate directory for later analysis.

This has been backported from master,
commit 84c08e4e

Signed-off-by: default avatarBalazs Lecz <leczb@google.com>
Reviewed-by: default avatarMichael Hanselmann <hansmi@google.com>
parent 855bffd2
......@@ -442,6 +442,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,
......@@ -475,6 +476,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())
......@@ -744,6 +746,7 @@ HVC_DEFAULTS = {
HV_SECURITY_DOMAIN: '',
HV_KVM_FLAG: "",
HV_VHOST_NET: False,
HV_KVM_USE_CHROOT: False,
},
HT_FAKE: {
},
......
......@@ -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,
......@@ -89,6 +99,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+)',
......@@ -231,6 +242,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
......@@ -248,7 +266,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)
......@@ -260,6 +278,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.
......@@ -531,6 +567,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
......@@ -646,6 +685,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")
......
......@@ -672,7 +672,24 @@
<simpara>This option is only effective with kvm versions >= 87
and qemu-kvm versions >= 0.11.0.
</simpara>
</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>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment