Move configurable mkfs to privsep.

Nova allows deployers to configure the command line which is used to create
a filesystem of a given type. This is frankly a little bit weird, but its
also historical. Move this functionality to privsep, including doing a
dance at startup to load config flags into privsep in a hopefully secure
manner.

Honestly, all of this code should be deprecated, but that's above my pay
grade and would take time to do. Oh, and maybe deployers love it the way
it is.

Change-Id: Id8eeb21e10f98a448946f178c8c5a36e48c7cac6
blueprint: hurrah-for-privsep
This commit is contained in:
Michael Still 2018-03-12 19:40:40 +11:00
parent 144d621397
commit 0751ee19d8
9 changed files with 200 additions and 125 deletions

View File

@ -17,6 +17,9 @@
Helpers for filesystem related routines.
"""
import hashlib
import six
from oslo_concurrency import processutils
from oslo_log import log as logging
@ -222,6 +225,81 @@ def ext_journal_enable(device):
processutils.execute('tune2fs', '-j', device)
# NOTE(mikal): nova allows deployers to configure the command line which is
# used to create a filesystem of a given type. This is frankly a little bit
# weird, but its also historical and probably should be in some sort of
# museum. So, we do that thing here, but it requires a funny dance in order
# to load that configuration at startup.
# NOTE(mikal): I really feel like this whole thing should be deprecated, I
# just don't think its a great idea to let people specify a command in a
# configuration option to run a root.
_MKFS_COMMAND = {}
_DEFAULT_MKFS_COMMAND = None
FS_FORMAT_EXT2 = "ext2"
FS_FORMAT_EXT3 = "ext3"
FS_FORMAT_EXT4 = "ext4"
FS_FORMAT_XFS = "xfs"
FS_FORMAT_NTFS = "ntfs"
FS_FORMAT_VFAT = "vfat"
SUPPORTED_FS_TO_EXTEND = (
FS_FORMAT_EXT2,
FS_FORMAT_EXT3,
FS_FORMAT_EXT4)
_DEFAULT_FILE_SYSTEM = FS_FORMAT_VFAT
_DEFAULT_FS_BY_OSTYPE = {'linux': FS_FORMAT_EXT4,
'windows': FS_FORMAT_NTFS}
def load_mkfs_command(os_type, command):
global _MKFS_COMMAND
global _DEFAULT_MKFS_COMMAND
_MKFS_COMMAND[os_type] = command
if os_type == 'default':
_DEFAULT_MKFS_COMMAND = command
def get_fs_type_for_os_type(os_type):
global _MKFS_COMMAND
return os_type if _MKFS_COMMAND.get(os_type) else 'default'
# NOTE(mikal): this method needs to be duplicated from utils because privsep
# can't depend on code outside the privsep directory.
def _get_hash_str(base_str):
"""Returns string that represents MD5 hash of base_str (in hex format).
If base_str is a Unicode string, encode it to UTF-8.
"""
if isinstance(base_str, six.text_type):
base_str = base_str.encode('utf-8')
return hashlib.md5(base_str).hexdigest()
def get_file_extension_for_os_type(os_type, default_ephemeral_format,
specified_fs=None):
global _MKFS_COMMAND
global _DEFAULT_MKFS_COMMAND
mkfs_command = _MKFS_COMMAND.get(os_type, _DEFAULT_MKFS_COMMAND)
if mkfs_command:
extension = mkfs_command
else:
if not specified_fs:
specified_fs = default_ephemeral_format
if not specified_fs:
specified_fs = _DEFAULT_FS_BY_OSTYPE.get(os_type,
_DEFAULT_FILE_SYSTEM)
extension = specified_fs
return _get_hash_str(extension)[:7]
@nova.privsep.sys_admin_pctxt.entrypoint
def mkfs(fs, path, label=None):
unprivileged_mkfs(fs, path, label=None)
@ -251,3 +329,41 @@ def unprivileged_mkfs(fs, path, label=None):
args.extend([label_opt, label])
args.append(path)
processutils.execute(*args)
@nova.privsep.sys_admin_pctxt.entrypoint
def _inner_configurable_mkfs(os_type, fs_label, target):
mkfs_command = (_MKFS_COMMAND.get(os_type, _DEFAULT_MKFS_COMMAND) or
'') % {'fs_label': fs_label, 'target': target}
processutils.execute(*mkfs_command.split())
# NOTE(mikal): this method is deliberately not wrapped in a privsep entrypoint
def configurable_mkfs(os_type, fs_label, target, run_as_root,
default_ephemeral_format, specified_fs=None):
# Format a file or block device using a user provided command for each
# os type. If user has not provided any configuration, format type will
# be used according to a default_ephemeral_format configuration or a
# system default.
global _MKFS_COMMAND
global _DEFAULT_MKFS_COMMAND
mkfs_command = (_MKFS_COMMAND.get(os_type, _DEFAULT_MKFS_COMMAND) or
'') % {'fs_label': fs_label, 'target': target}
if mkfs_command:
if run_as_root:
_inner_configurable_mkfs(os_type, fs_label, target)
else:
processutils.execute(*mkfs_command.split())
else:
if not specified_fs:
specified_fs = default_ephemeral_format
if not specified_fs:
specified_fs = _DEFAULT_FS_BY_OSTYPE.get(os_type,
_DEFAULT_FILE_SYSTEM)
if run_as_root:
mkfs(specified_fs, target, fs_label)
else:
unprivileged_mkfs(specified_fs, target, fs_label)

View File

@ -71,3 +71,49 @@ class MkfsTestCase(test.NoDBTestCase):
'swap', '/my/swap/block/dev', 'swap-vol')
mock_execute.assert_called_once_with(
'mkswap', '-L', 'swap-vol', '/my/swap/block/dev')
HASH_VFAT = nova.privsep.fs._get_hash_str(
nova.privsep.fs.FS_FORMAT_VFAT)[:7]
HASH_EXT4 = nova.privsep.fs._get_hash_str(
nova.privsep.fs.FS_FORMAT_EXT4)[:7]
HASH_NTFS = nova.privsep.fs._get_hash_str(
nova.privsep.fs.FS_FORMAT_NTFS)[:7]
def test_get_file_extension_for_os_type(self):
self.assertEqual(self.HASH_VFAT,
nova.privsep.fs.get_file_extension_for_os_type(
None, None))
self.assertEqual(self.HASH_EXT4,
nova.privsep.fs.get_file_extension_for_os_type(
'linux', None))
self.assertEqual(self.HASH_NTFS,
nova.privsep.fs.get_file_extension_for_os_type(
'windows', None))
def test_get_file_extension_for_os_type_with_overrides(self):
with mock.patch('nova.privsep.fs._DEFAULT_MKFS_COMMAND',
'custom mkfs command'):
self.assertEqual("a74d253",
nova.privsep.fs.get_file_extension_for_os_type(
'linux', None))
self.assertEqual("a74d253",
nova.privsep.fs.get_file_extension_for_os_type(
'windows', None))
self.assertEqual("a74d253",
nova.privsep.fs.get_file_extension_for_os_type(
'osx', None))
with mock.patch.dict(nova.privsep.fs._MKFS_COMMAND,
{'osx': 'custom mkfs command'}, clear=True):
self.assertEqual(self.HASH_VFAT,
nova.privsep.fs.get_file_extension_for_os_type(
None, None))
self.assertEqual(self.HASH_EXT4,
nova.privsep.fs.get_file_extension_for_os_type(
'linux', None))
self.assertEqual(self.HASH_NTFS,
nova.privsep.fs.get_file_extension_for_os_type(
'windows', None))
self.assertEqual("a74d253",
nova.privsep.fs.get_file_extension_for_os_type(
'osx', None))

View File

@ -192,41 +192,3 @@ class APITestCase(test.NoDBTestCase):
mock_resize.assert_called_once_with(imgfile, run_as_root=False,
check_exit_code=[0])
mock_can_resize.assert_called_once_with(imgfile, imgsize)
HASH_VFAT = utils.get_hash_str(api.FS_FORMAT_VFAT)[:7]
HASH_EXT4 = utils.get_hash_str(api.FS_FORMAT_EXT4)[:7]
HASH_NTFS = utils.get_hash_str(api.FS_FORMAT_NTFS)[:7]
def test_get_file_extension_for_os_type(self):
self.assertEqual(self.HASH_VFAT,
api.get_file_extension_for_os_type(None, None))
self.assertEqual(self.HASH_EXT4,
api.get_file_extension_for_os_type('linux', None))
self.assertEqual(self.HASH_NTFS,
api.get_file_extension_for_os_type(
'windows', None))
def test_get_file_extension_for_os_type_with_overrides(self):
with mock.patch('nova.virt.disk.api._DEFAULT_MKFS_COMMAND',
'custom mkfs command'):
self.assertEqual("a74d253",
api.get_file_extension_for_os_type(
'linux', None))
self.assertEqual("a74d253",
api.get_file_extension_for_os_type(
'windows', None))
self.assertEqual("a74d253",
api.get_file_extension_for_os_type('osx', None))
with mock.patch.dict(api._MKFS_COMMAND,
{'osx': 'custom mkfs command'}, clear=True):
self.assertEqual(self.HASH_VFAT,
api.get_file_extension_for_os_type(None, None))
self.assertEqual(self.HASH_EXT4,
api.get_file_extension_for_os_type('linux', None))
self.assertEqual(self.HASH_NTFS,
api.get_file_extension_for_os_type(
'windows', None))
self.assertEqual("a74d253",
api.get_file_extension_for_os_type(
'osx', None))

View File

@ -73,6 +73,7 @@ from nova.objects import migrate_data as migrate_data_obj
from nova.objects import virtual_interface as obj_vif
from nova.pci import manager as pci_manager
from nova.pci import utils as pci_utils
import nova.privsep.fs
import nova.privsep.libvirt
from nova import rc_fields
from nova import test
@ -92,7 +93,6 @@ from nova.tests import uuidsentinel as uuids
from nova import utils
from nova import version
from nova.virt import block_device as driver_block_device
from nova.virt.disk import api as disk_api
from nova.virt import driver
from nova.virt import fake
from nova.virt import firewall as base_firewall
@ -346,7 +346,7 @@ _fake_cpu_info = {
"features": ["feature1", "feature2"]
}
eph_default_ext = utils.get_hash_str(disk_api._DEFAULT_FILE_SYSTEM)[:7]
eph_default_ext = utils.get_hash_str(nova.privsep.fs._DEFAULT_FILE_SYSTEM)[:7]
def eph_name(size):
@ -11876,7 +11876,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
self.stubs.Set(drvr, '_create_domain_and_network', fake_none)
self.stubs.Set(drvr, 'get_info', fake_get_info)
if mkfs:
self.stubs.Set(nova.virt.disk.api, '_MKFS_COMMAND',
self.stubs.Set(nova.privsep.fs, '_MKFS_COMMAND',
{os_type: 'mkfs.ext4 --label %(fs_label)s %(target)s'})
image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
@ -12120,7 +12120,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
host='fake-source-host',
receive=True)
@mock.patch('nova.virt.disk.api.get_file_extension_for_os_type')
@mock.patch('nova.privsep.fs.get_file_extension_for_os_type')
def test_create_image_with_ephemerals(self, mock_get_ext):
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
instance_ref = self.test_instance
@ -12225,27 +12225,27 @@ class LibvirtConnTestCase(test.NoDBTestCase,
fake_mkfs.assert_has_calls([mock.call('ext4', '/dev/something',
'myVol')])
def test_create_ephemeral_with_arbitrary(self):
@mock.patch('nova.privsep.fs.configurable_mkfs')
def test_create_ephemeral_with_arbitrary(self, fake_mkfs):
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
self.stubs.Set(nova.virt.disk.api, '_MKFS_COMMAND',
self.stubs.Set(nova.privsep.fs, '_MKFS_COMMAND',
{'linux': 'mkfs.ext4 --label %(fs_label)s %(target)s'})
self.mox.StubOutWithMock(utils, 'execute')
utils.execute('mkfs.ext4', '--label', 'myVol', '/dev/something',
run_as_root=True)
self.mox.ReplayAll()
drvr._create_ephemeral('/dev/something', 20, 'myVol', 'linux',
is_block_dev=True)
fake_mkfs.assert_has_calls([mock.call('linux', 'myVol',
'/dev/something', True, None,
None)])
def test_create_ephemeral_with_ext3(self):
@mock.patch('nova.privsep.fs.configurable_mkfs')
def test_create_ephemeral_with_ext3(self, fake_mkfs):
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
self.stubs.Set(nova.virt.disk.api, '_MKFS_COMMAND',
self.stubs.Set(nova.privsep.fs, '_MKFS_COMMAND',
{'linux': 'mkfs.ext3 --label %(fs_label)s %(target)s'})
self.mox.StubOutWithMock(utils, 'execute')
utils.execute('mkfs.ext3', '--label', 'myVol', '/dev/something',
run_as_root=True)
self.mox.ReplayAll()
drvr._create_ephemeral('/dev/something', 20, 'myVol', 'linux',
is_block_dev=True)
fake_mkfs.assert_has_calls([mock.call('linux', 'myVol',
'/dev/something', True, None,
None)])
@mock.patch.object(fake_libvirt_utils, 'create_ploop_image')
def test_create_ephemeral_parallels(self, mock_create_ploop):
@ -15517,8 +15517,10 @@ class LibvirtConnTestCase(test.NoDBTestCase,
self._test_get_device_name_for_instance(new_bdm, '/dev/fda')
def test_is_supported_fs_format(self):
supported_fs = [disk_api.FS_FORMAT_EXT2, disk_api.FS_FORMAT_EXT3,
disk_api.FS_FORMAT_EXT4, disk_api.FS_FORMAT_XFS]
supported_fs = [nova.privsep.fs.FS_FORMAT_EXT2,
nova.privsep.fs.FS_FORMAT_EXT3,
nova.privsep.fs.FS_FORMAT_EXT4,
nova.privsep.fs.FS_FORMAT_XFS]
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
for fs in supported_fs:
self.assertTrue(drvr.is_supported_fs_format(fs))

View File

@ -28,6 +28,7 @@ from nova import context
from nova import exception
from nova import objects
from nova.objects import fields as obj_fields
import nova.privsep.fs
from nova import test
from nova.tests.unit import fake_instance
from nova.tests.unit.virt.libvirt import fakelibvirt
@ -368,7 +369,7 @@ ID TAG VM SIZE DATE VM CLOCK
'expected_fs_type': 'some_fs_type'},
{'fs_type': None,
'default_eph_format': None,
'expected_fs_type': disk.FS_FORMAT_EXT4},
'expected_fs_type': nova.privsep.fs.FS_FORMAT_EXT4},
{'fs_type': None,
'default_eph_format': 'eph_format',
'expected_fs_type': 'eph_format'})

View File

@ -50,77 +50,23 @@ LOG = logging.getLogger(__name__)
CONF = nova.conf.CONF
_MKFS_COMMAND = {}
_DEFAULT_MKFS_COMMAND = None
FS_FORMAT_EXT2 = "ext2"
FS_FORMAT_EXT3 = "ext3"
FS_FORMAT_EXT4 = "ext4"
FS_FORMAT_XFS = "xfs"
FS_FORMAT_NTFS = "ntfs"
FS_FORMAT_VFAT = "vfat"
# NOTE(mikal): Here as a transition step
SUPPORTED_FS_TO_EXTEND = nova.privsep.fs.SUPPORTED_FS_TO_EXTEND
SUPPORTED_FS_TO_EXTEND = (
FS_FORMAT_EXT2,
FS_FORMAT_EXT3,
FS_FORMAT_EXT4)
_DEFAULT_FILE_SYSTEM = FS_FORMAT_VFAT
_DEFAULT_FS_BY_OSTYPE = {'linux': FS_FORMAT_EXT4,
'windows': FS_FORMAT_NTFS}
for s in CONF.virt_mkfs:
# NOTE(yamahata): mkfs command may includes '=' for its options.
# So item.partition('=') doesn't work here
os_type, mkfs_command = s.split('=', 1)
if os_type:
_MKFS_COMMAND[os_type] = mkfs_command
if os_type == 'default':
_DEFAULT_MKFS_COMMAND = mkfs_command
def get_fs_type_for_os_type(os_type):
return os_type if _MKFS_COMMAND.get(os_type) else 'default'
def get_file_extension_for_os_type(os_type, specified_fs=None):
mkfs_command = _MKFS_COMMAND.get(os_type, _DEFAULT_MKFS_COMMAND)
if mkfs_command:
extension = mkfs_command
else:
if not specified_fs:
specified_fs = CONF.default_ephemeral_format
if not specified_fs:
specified_fs = _DEFAULT_FS_BY_OSTYPE.get(os_type,
_DEFAULT_FILE_SYSTEM)
extension = specified_fs
return utils.get_hash_str(extension)[:7]
nova.privsep.fs.load_mkfs_command(os_type, mkfs_command)
def mkfs(os_type, fs_label, target, run_as_root=True, specified_fs=None):
"""Format a file or block device using
a user provided command for each os type.
If user has not provided any configuration,
format type will be used according to a
default_ephemeral_format configuration
or a system defaults.
"""
mkfs_command = (_MKFS_COMMAND.get(os_type, _DEFAULT_MKFS_COMMAND) or
'') % {'fs_label': fs_label, 'target': target}
if mkfs_command:
utils.execute(*mkfs_command.split(), run_as_root=run_as_root)
else:
if not specified_fs:
specified_fs = CONF.default_ephemeral_format
if not specified_fs:
specified_fs = _DEFAULT_FS_BY_OSTYPE.get(os_type,
_DEFAULT_FILE_SYSTEM)
if run_as_root:
nova.privsep.fs.mkfs(specified_fs, target, fs_label)
else:
nova.privsep.fs.unprivileged_mkfs(specified_fs, target, fs_label)
nova.privsep.fs.configurable_mkfs(
os_type, fs_label, target, run_as_root,
CONF.default_ephemeral_format, specified_fs)
def resize2fs(image, check_exit_code=False, run_as_root=False):

View File

@ -3460,12 +3460,12 @@ class LibvirtDriver(driver.ComputeDriver):
fallback_from_host)
# Lookup the filesystem type if required
os_type_with_default = disk_api.get_fs_type_for_os_type(
os_type_with_default = nova.privsep.fs.get_fs_type_for_os_type(
instance.os_type)
# Generate a file extension based on the file system
# type and the mkfs commands configured if any
file_extension = disk_api.get_file_extension_for_os_type(
os_type_with_default)
file_extension = nova.privsep.fs.get_file_extension_for_os_type(
os_type_with_default, CONF.default_ephemeral_format)
vm_mode = fields.VMMode.get_from_instance(instance)
ephemeral_gb = instance.flavor.ephemeral_gb
@ -8710,5 +8710,7 @@ class LibvirtDriver(driver.ComputeDriver):
return block_device.prepend_dev(disk_info['dev'])
def is_supported_fs_format(self, fs_type):
return fs_type in [disk_api.FS_FORMAT_EXT2, disk_api.FS_FORMAT_EXT3,
disk_api.FS_FORMAT_EXT4, disk_api.FS_FORMAT_XFS]
return fs_type in [nova.privsep.fs.FS_FORMAT_EXT2,
nova.privsep.fs.FS_FORMAT_EXT3,
nova.privsep.fs.FS_FORMAT_EXT4,
nova.privsep.fs.FS_FORMAT_XFS]

View File

@ -29,10 +29,10 @@ from oslo_utils import fileutils
import nova.conf
from nova.i18n import _
from nova.objects import fields as obj_fields
import nova.privsep.fs
import nova.privsep.idmapshift
import nova.privsep.libvirt
from nova import utils
from nova.virt.disk import api as disk
from nova.virt import images
from nova.virt.libvirt import config as vconfig
from nova.virt.libvirt.volume import remotefs
@ -105,7 +105,7 @@ def create_ploop_image(disk_format, path, size, fs_type):
"""
if not fs_type:
fs_type = CONF.default_ephemeral_format or \
disk.FS_FORMAT_EXT4
nova.privsep.fs.FS_FORMAT_EXT4
fileutils.ensure_tree(path)
disk_path = os.path.join(path, 'root.hds')
nova.privsep.libvirt.ploop_init(size, disk_format, fs_type, disk_path)

View File

@ -2,4 +2,4 @@
upgrade:
- |
The following commands are no longer required to be listed in your rootwrap
configuration: tune2fs.
configuration: mkfs; tune2fs.