Move libvirts dmcrypt support to privsep.
This is the first example of something where we can't just move to python calls on the far side of the trust boundary. So we just move the executes() to the trusted sie and make sure their attack surface is as small as possible. Change-Id: Ib8d3b48f5a1482a7a040e58bec6b3a599c8c6fd0 blueprint: hurrah-for-privsep
This commit is contained in:
parent
90e91ca052
commit
a059e70486
|
@ -17,9 +17,12 @@
|
|||
libvirt specific routines.
|
||||
"""
|
||||
|
||||
import binascii
|
||||
import errno
|
||||
import os
|
||||
|
||||
from oslo_concurrency import processutils
|
||||
|
||||
import nova.privsep
|
||||
|
||||
|
||||
|
@ -56,6 +59,36 @@ def _last_bytes_inner(file_like_object, num):
|
|||
return (file_like_object.read(), remaining)
|
||||
|
||||
|
||||
@nova.privsep.sys_admin_pctxt.entrypoint
|
||||
def dmcrypt_create_volume(target, device, cipher, key_size, key):
|
||||
"""Sets up a dmcrypt mapping
|
||||
|
||||
:param target: device mapper logical device name
|
||||
:param device: underlying block device
|
||||
:param cipher: encryption cipher string digestible by cryptsetup
|
||||
:param key_size: encryption key size
|
||||
:param key: encoded encryption key bytestring
|
||||
"""
|
||||
cmd = ('cryptsetup',
|
||||
'create',
|
||||
target,
|
||||
device,
|
||||
'--cipher=' + cipher,
|
||||
'--key-size=' + str(key_size),
|
||||
'--key-file=-')
|
||||
key = binascii.hexlify(key).decode('utf-8')
|
||||
processutils.execute(*cmd, process_input=key)
|
||||
|
||||
|
||||
@nova.privsep.sys_admin_pctxt.entrypoint
|
||||
def dmcrypt_delete_volume(target):
|
||||
"""Deletes a dmcrypt mapping
|
||||
|
||||
:param target: name of the mapped logical device
|
||||
"""
|
||||
processutils.execute('cryptsetup', 'remove', target)
|
||||
|
||||
|
||||
@nova.privsep.sys_admin_pctxt.entrypoint
|
||||
def enable_hairpin(interface):
|
||||
"""Enable hairpin mode for a libvirt guest."""
|
||||
|
|
|
@ -33,21 +33,18 @@ class LibvirtDmcryptTestCase(test.NoDBTestCase):
|
|||
self.KEY = bytes(bytearray(x for x in range(0, self.KEY_SIZE)))
|
||||
self.KEY_STR = binascii.hexlify(self.KEY).decode('utf-8')
|
||||
|
||||
@mock.patch('nova.utils.execute')
|
||||
@mock.patch('nova.privsep.libvirt.dmcrypt_create_volume')
|
||||
def test_create_volume(self, mock_execute):
|
||||
dmcrypt.create_volume(self.TARGET, self.PATH, self.CIPHER,
|
||||
self.KEY_SIZE, self.KEY)
|
||||
|
||||
mock_execute.assert_has_calls([
|
||||
mock.call('cryptsetup', 'create', self.TARGET, self.PATH,
|
||||
'--cipher=' + self.CIPHER,
|
||||
'--key-size=' + str(self.KEY_SIZE),
|
||||
'--key-file=-', process_input=self.KEY_STR,
|
||||
run_as_root=True),
|
||||
mock.call(self.TARGET, self.PATH, self.CIPHER, self.KEY_SIZE,
|
||||
self.KEY)
|
||||
])
|
||||
|
||||
@mock.patch('nova.virt.libvirt.storage.dmcrypt.LOG')
|
||||
@mock.patch('nova.utils.execute')
|
||||
@mock.patch('nova.privsep.libvirt.dmcrypt_create_volume')
|
||||
def test_create_volume_fail(self, mock_execute, mock_log):
|
||||
mock_execute.side_effect = processutils.ProcessExecutionError()
|
||||
|
||||
|
@ -58,16 +55,16 @@ class LibvirtDmcryptTestCase(test.NoDBTestCase):
|
|||
self.assertEqual(1, mock_execute.call_count)
|
||||
self.assertEqual(1, mock_log.error.call_count) # error logged
|
||||
|
||||
@mock.patch('nova.utils.execute')
|
||||
@mock.patch('nova.privsep.libvirt.dmcrypt_delete_volume')
|
||||
def test_delete_volume(self, mock_execute):
|
||||
dmcrypt.delete_volume(self.TARGET)
|
||||
|
||||
mock_execute.assert_has_calls([
|
||||
mock.call('cryptsetup', 'remove', self.TARGET, run_as_root=True),
|
||||
mock.call(self.TARGET),
|
||||
])
|
||||
|
||||
@mock.patch('nova.virt.libvirt.storage.dmcrypt.LOG')
|
||||
@mock.patch('nova.utils.execute')
|
||||
@mock.patch('nova.privsep.libvirt.dmcrypt_delete_volume')
|
||||
def test_delete_volume_fail(self, mock_execute, mock_log):
|
||||
mock_execute.side_effect = processutils.ProcessExecutionError()
|
||||
|
||||
|
@ -78,7 +75,7 @@ class LibvirtDmcryptTestCase(test.NoDBTestCase):
|
|||
self.assertEqual(1, mock_log.error.call_count) # error logged
|
||||
|
||||
@mock.patch('nova.virt.libvirt.storage.dmcrypt.LOG')
|
||||
@mock.patch('nova.utils.execute')
|
||||
@mock.patch('nova.privsep.libvirt.dmcrypt_delete_volume')
|
||||
def test_delete_missing_volume(self, mock_execute, mock_log):
|
||||
mock_execute.side_effect = \
|
||||
processutils.ProcessExecutionError(exit_code=4)
|
||||
|
|
|
@ -13,14 +13,13 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import binascii
|
||||
import os
|
||||
|
||||
from oslo_concurrency import processutils
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
|
||||
from nova import utils
|
||||
import nova.privsep.libvirt
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
@ -54,16 +53,9 @@ def create_volume(target, device, cipher, key_size, key):
|
|||
:param key_size: encryption key size
|
||||
:param key: encoded encryption key bytestring
|
||||
"""
|
||||
cmd = ('cryptsetup',
|
||||
'create',
|
||||
target,
|
||||
device,
|
||||
'--cipher=' + cipher,
|
||||
'--key-size=' + str(key_size),
|
||||
'--key-file=-')
|
||||
key = binascii.hexlify(key).decode('utf-8')
|
||||
try:
|
||||
utils.execute(*cmd, process_input=key, run_as_root=True)
|
||||
nova.privsep.libvirt.dmcrypt_create_volume(
|
||||
target, device, cipher, key_size, key)
|
||||
except processutils.ProcessExecutionError as e:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error("Could not start encryption for disk %(device)s: "
|
||||
|
@ -76,7 +68,7 @@ def delete_volume(target):
|
|||
:param target: name of the mapped logical device
|
||||
"""
|
||||
try:
|
||||
utils.execute('cryptsetup', 'remove', target, run_as_root=True)
|
||||
nova.privsep.libvirt.dmcrypt_delete_volume(target)
|
||||
except processutils.ProcessExecutionError as e:
|
||||
# cryptsetup returns 4 when attempting to destroy a non-existent
|
||||
# dm-crypt device. It indicates that the device is invalid, which
|
||||
|
|
|
@ -5,4 +5,4 @@ upgrade:
|
|||
rootwrap configuration.
|
||||
- |
|
||||
The following commands are no longer required to be listed in your rootwrap
|
||||
configuration: cat; chown; readlink; tee; touch.
|
||||
configuration: cat; chown; cryptsetup; readlink; tee; touch.
|
Loading…
Reference in New Issue