[blivet] Allow specifying minimum entropy when creating LUKS
Vratislav Podzimek
vpodzime at redhat.com
Sun Aug 31 19:43:48 UTC 2014
On Fri, 2014-08-29 at 11:02 -0400, Anne Mulhern wrote:
>
>
>
> ----- Original Message -----
> > From: "Vratislav Podzimek" <vpodzime at redhat.com>
> > To: anaconda-patches at lists.fedorahosted.org
> > Sent: Friday, August 29, 2014 8:21:12 AM
> > Subject: [blivet] Allow specifying minimum entropy when creating LUKS
> >
> > Creating new LUKS format requires high-quality random data and such quality
> > may
> > be specified by minimum entropy of kernel's random data pool required. Blivet
> > thus need to check such entropy and wait for it to become high enough if
> > requested by the user code (Anaconda, OpenLMI, blivet-gui, etc.).
> >
> > In order to allow the user code to give a hint to the users about what's
> > happening and how they can help with entropy generation, blivet also needs a
> > callback that is called in case of LUKS format being created with not enough
> > entropy available.
> >
> > Related: rhbz#1073679
> > Signed-off-by: Vratislav Podzimek <vpodzime at redhat.com>
> > ---
> > blivet/callbacks.py | 9 ++++++---
> > blivet/deviceaction.py | 13 ++++++++++++-
> > blivet/devicefactory.py | 13 ++++++++++---
> > blivet/devicelibs/crypto.py | 11 ++++++++++-
> > blivet/formats/luks.py | 8 +++++++-
> > blivet/partitioning.py | 25 ++++++++++++++++++-------
> > blivet/util.py | 4 ++++
> > 7 files changed, 67 insertions(+), 16 deletions(-)
> >
> > diff --git a/blivet/callbacks.py b/blivet/callbacks.py
> > index 2f7a9cf..b2e8c3a 100644
> > --- a/blivet/callbacks.py
> > +++ b/blivet/callbacks.py
> > @@ -34,12 +34,14 @@ _CallbacksRegister = namedtuple("_CallbacksRegister",
> > ["create_format_pre",
> > "create_format_post",
> > "resize_format_pre",
> > - "resize_format_post"])
> > + "resize_format_post",
> > + "wait_for_entropy"])
> >
> > def create_new_callbacks_register(create_format_pre=None,
> > create_format_post=None,
> > resize_format_pre=None,
> > - resize_format_post=None):
> > + resize_format_post=None,
> > + wait_for_entropy=None):
> > """
> > A function for creating a new opaque object holding the references to
> > callbacks. The point of this function is to hide the implementation of
> > such
> > @@ -49,7 +51,8 @@ def create_new_callbacks_register(create_format_pre=None,
> > """
> >
> > return _CallbacksRegister(create_format_pre, create_format_post,
> > - resize_format_pre, resize_format_post)
> > + resize_format_pre, resize_format_post,
> > + wait_for_entropy)
> >
> > CreateFormatPreData = namedtuple("CreateFormatPreData",
> > ["msg"])
> > diff --git a/blivet/deviceaction.py b/blivet/deviceaction.py
> > index fff5f39..a17ade1 100644
> > --- a/blivet/deviceaction.py
> > +++ b/blivet/deviceaction.py
> > @@ -24,10 +24,11 @@
> > from . import util
> >
> > from . import udev
> > +from .util import get_current_entropy
> > from .devices import StorageDevice
> > from .devices import PartitionDevice
> > from .devices import LVMLogicalVolumeDevice
> > -from .formats import getFormat
> > +from .formats import getFormat, luks
> > from .storage_log import log_exception_info
> > from parted import partitionFlag, PARTITION_LBA
> > from .i18n import _, N_
> > @@ -552,6 +553,16 @@ class ActionCreateFormat(DeviceAction):
> >
> > self.device.disk.format.commitToDisk()
> >
> > + if isinstance(self.device.format, luks.LUKS):
> > + # LUKS needs to wait for random data entropy if it is too low
> > + required_entropy = self.device.format.min_luks_entropy
> > + current_entropy = get_current_entropy()
> > + if required_entropy > 0 and current_entropy < required_entropy:
>
> minimum entropy less than 0 treated as expected. Also, might be useful to rename
> required_entropy min_required_entropy here.
I think it's quite obvious from the context, but it will make things a
bit clearer which is always good. Fixing locally.
>
> > + if callbacks and callbacks.wait_for_entropy:
> > + msg = _("Not enough entropy to create LUKS format. "
> > + "%d bits are needed.") % required_entropy
> > + callbacks.wait_for_entropy(msg, required_entropy)
> > +
> > self.device.format.create(device=self.device.path,
> > options=self.device.formatArgs)
> > # Get the UUID now that the format is created
> > diff --git a/blivet/devicefactory.py b/blivet/devicefactory.py
> > index 7926d39..4e123b0 100644
> > --- a/blivet/devicefactory.py
> > +++ b/blivet/devicefactory.py
> > @@ -218,7 +218,7 @@ class DeviceFactory(object):
> > label=None, raid_level=None, encrypted=False,
> > container_encrypted=False, container_name=None,
> > container_raid_level=None, container_size=SIZE_POLICY_AUTO,
> > - name=None, device=None):
> > + name=None, device=None, min_luks_entropy=0):
> > """
> > :param storage: a Blivet instance
> > :type storage: :class:`~.Blivet`
> > @@ -258,6 +258,9 @@ class DeviceFactory(object):
> > :type container_encrypted: bool
> > :keyword container_size: requested container size
> > :type container_size: :class:`~.size.Size`
> > + :keyword min_luks_entropy: minimum entropy in bits required for
> > + LUKS format creation
> > + :type min_luks_entropy: int
> >
> > """
> >
> > @@ -297,6 +300,7 @@ class DeviceFactory(object):
> >
> > self.child_factory = None
> > self.parent_factory = None
> > + self.min_luks_entropy = min_luks_entropy
> >
> > # used for error recovery
> > self.__devices = []
> > @@ -607,6 +611,7 @@ class DeviceFactory(object):
> >
> > fmt = getFormat(self.fstype,
> > mountpoint=self.mountpoint,
> > + min_luks_entropy=self.min_luks_entropy,
> > **fmt_args)
> > luks_device = LUKSDevice("luks-" + device.name,
> > parents=[device], fmt=fmt)
> > @@ -685,7 +690,8 @@ class DeviceFactory(object):
> > if parent_container:
> > parent_container.parents.remove(self.device)
> > leaf_format = self.device.format
> > - self.storage.formatDevice(self.device, getFormat("luks"))
> > + self.storage.formatDevice(self.device, getFormat("luks",
> > +
> > min_luks_entropy=self.min_luks_entropy))
> > luks_device = LUKSDevice("luks-%s" % self.device.name,
> > fmt=leaf_format,
> > parents=self.device)
> > @@ -1026,7 +1032,8 @@ class PartitionSetFactory(PartitionFactory):
> > if container:
> > container.parents.remove(member)
> >
> > - self.storage.formatDevice(member, getFormat("luks"))
> > + self.storage.formatDevice(member, getFormat("luks",
> > +
> > min_luks_entropy=self.min_luks_entropy))
> > luks_member = LUKSDevice("luks-%s" % member.name,
> > parents=[member],
> > fmt=getFormat(self.fstype))
> > diff --git a/blivet/devicelibs/crypto.py b/blivet/devicelibs/crypto.py
> > index b911e94..ccc8aa3 100644
> > --- a/blivet/devicelibs/crypto.py
> > +++ b/blivet/devicelibs/crypto.py
> > @@ -21,12 +21,15 @@
> > #
> >
> > import random
> > +import time
> > from pycryptsetup import CryptSetup
> >
> > from ..errors import CryptoError
> > from ..size import Size
> > +from ..util import get_current_entropy
> >
> > LUKS_METADATA_SIZE = Size("2 MiB")
> > +MIN_CREATE_ENTROPY = 256 # bits
> >
> > # Keep the character set size a power of two to make sure all characters are
> > # equally likely
> > @@ -65,7 +68,8 @@ def luks_status(name):
> >
> > def luks_format(device,
> > passphrase=None,
> > - cipher=None, key_size=None, key_file=None):
> > + cipher=None, key_size=None, key_file=None,
> > + min_entropy=0):
> > # pylint: disable=unused-argument
> > if not passphrase:
> > raise ValueError("luks_format requires passphrase")
> > @@ -88,6 +92,11 @@ def luks_format(device,
> > if cipherMode: kwargs["cipherMode"] = cipherMode
> > if key_size: kwargs["keysize"] = key_size
> >
> > + if min_entropy >= 0:
>
> minimum entropy less than zero treated as expected value.
>
> > + while get_current_entropy() < min_entropy:
> > + # wait for entropy to become high enough
> > + time.sleep(1)
> > +
> > rc = cs.luksFormat(**kwargs)
> > if rc:
> > raise CryptoError("luks_format failed for '%s'" % device)
> > diff --git a/blivet/formats/luks.py b/blivet/formats/luks.py
> > index bcececb..d01bc6a 100644
> > --- a/blivet/formats/luks.py
> > +++ b/blivet/formats/luks.py
> > @@ -71,6 +71,9 @@ class LUKS(DeviceFormat):
> > :type escrow_cert: str
> > :keyword add_backup_passphrase: generate a backup passphrase?
> > :type add_backup_passphrase: bool.
> > + :keyword min_luks_entropy: minimum entropy in bits required for
> > + format creation
> > + :type min_luks_entropy: int
> >
> > .. note::
> >
> > @@ -97,6 +100,7 @@ class LUKS(DeviceFormat):
> > self._key_file = kwargs.get("key_file")
> > self.escrow_cert = kwargs.get("escrow_cert")
> > self.add_backup_passphrase = kwargs.get("add_backup_passphrase",
> > False)
> > + self.min_luks_entropy = kwargs.get("min_luks_entropy", 0)
> >
> > if not self.mapName and self.exists and self.uuid:
> > self.mapName = "luks-%s" % self.uuid
> > @@ -220,7 +224,9 @@ class LUKS(DeviceFormat):
> > passphrase=self.__passphrase,
> > key_file=self._key_file,
> > cipher=self.cipher,
> > - key_size=self.key_size)
> > + key_size=self.key_size,
> > + min_entropy=self.min_luks_entropy)
> > +
> > except Exception:
> > raise
> > else:
> > diff --git a/blivet/partitioning.py b/blivet/partitioning.py
> > index 48d72ff..32fcf68 100644
> > --- a/blivet/partitioning.py
> > +++ b/blivet/partitioning.py
> > @@ -69,7 +69,7 @@ def _getCandidateDisks(storage):
> >
> > return disks
> >
> > -def _scheduleImplicitPartitions(storage, disks):
> > +def _scheduleImplicitPartitions(storage, disks, min_luks_entropy=0):
> > """ Schedule creation of a lvm/btrfs member partitions for autopart.
> >
> > We create one such partition on each disk. They are not allocated
> > until
> > @@ -79,6 +79,9 @@ def _scheduleImplicitPartitions(storage, disks):
> > :type storage: :class:`~.Blivet`
> > :param disks: list of partitioned disks with free space
> > :type disks: list of :class:`~.devices.StorageDevice`
> > + :param min_luks_entropy: minimum entropy in bits required for
> > + luks format creation
> > + :type min_luks_entropy: int
> > :returns: list of newly created (unallocated) partitions
> > :rtype: list of :class:`~.devices.PartitionDevice`
> > """
> > @@ -95,7 +98,8 @@ def _scheduleImplicitPartitions(storage, disks):
> > fmt_args = {"passphrase": storage.encryptionPassphrase,
> > "cipher": storage.encryptionCipher,
> > "escrow_cert": storage.autoPartEscrowCert,
> > - "add_backup_passphrase":
> > storage.autoPartAddBackupPassphrase}
> > + "add_backup_passphrase":
> > storage.autoPartAddBackupPassphrase,
> > + "min_luks_entropy": min_luks_entropy}
> > else:
> > if storage.autoPartType in (AUTOPART_TYPE_LVM,
> > AUTOPART_TYPE_LVM_THINP):
> > fmt_type = "lvmpv"
> > @@ -111,7 +115,7 @@ def _scheduleImplicitPartitions(storage, disks):
> >
> > return devs
> >
> > -def _schedulePartitions(storage, disks):
> > +def _schedulePartitions(storage, disks, min_luks_entropy=0):
> > """ Schedule creation of autopart partitions.
> >
> > This only schedules the requests for actual partitions.
> > @@ -120,6 +124,9 @@ def _schedulePartitions(storage, disks):
> > :type storage: :class:`~.Blivet`
> > :param disks: list of partitioned disks with free space
> > :type disks: list of :class:`~.devices.StorageDevice`
> > + :param min_luks_entropy: minimum entropy in bits required for
> > + luks format creation
> > + :type min_luks_entropy: int
> > :returns: None
> > :rtype: None
> > """
> > @@ -196,7 +203,8 @@ def _schedulePartitions(storage, disks):
> > fmt_args = {"passphrase": storage.encryptionPassphrase,
> > "cipher": storage.encryptionCipher,
> > "escrow_cert": storage.autoPartEscrowCert,
> > - "add_backup_passphrase":
> > storage.autoPartAddBackupPassphrase}
> > + "add_backup_passphrase":
> > storage.autoPartAddBackupPassphrase,
> > + "min_luks_entropy": min_luks_entropy}
> > else:
> > fmt_type = request.fstype
> > fmt_args = {}
> > @@ -328,13 +336,16 @@ def _scheduleVolumes(storage, devs):
> > # schedule the device for creation
> > storage.createDevice(dev)
> >
> > -def doAutoPartition(storage, data):
> > +def doAutoPartition(storage, data, min_luks_entropy=0):
> > """ Perform automatic partitioning.
> >
> > :param storage: a :class:`~.Blivet` instance
> > :type storage: :class:`~.Blivet`
> > :param data: kickstart data
> > :type data: :class:`pykickstart.BaseHandler`
> > + :param min_luks_entropy: minimum entropy in bits required for
> > + luks format creation
> > + :type min_luks_entropy: int
> >
> > :attr:`Blivet.doAutoPart` controls whether this method creates the
> > automatic partitioning layout. :attr:`Blivet.autoPartType` controls
> > @@ -369,7 +380,7 @@ def doAutoPartition(storage, data):
> > raise NoDisksError(_("No usable disks selected"))
> >
> > disks = _getCandidateDisks(storage)
> > - devs = _scheduleImplicitPartitions(storage, disks)
> > + devs = _scheduleImplicitPartitions(storage, disks, min_luks_entropy)
> > log.debug("candidate disks: %s", disks)
> > log.debug("devs: %s", devs)
> >
> > @@ -377,7 +388,7 @@ def doAutoPartition(storage, data):
> > raise NotEnoughFreeSpaceError(_("Not enough free space on disks for
> > "
> > "automatic partitioning"))
> >
> > - _schedulePartitions(storage, disks)
> > + _schedulePartitions(storage, disks, min_luks_entropy=min_luks_entropy)
> >
> > # run the autopart function to allocate and grow partitions
> > doPartitioning(storage)
> > diff --git a/blivet/util.py b/blivet/util.py
> > index a15bb4a..649b8a7 100644
> > --- a/blivet/util.py
> > +++ b/blivet/util.py
> > @@ -454,3 +454,7 @@ def variable_copy(obj, memo, omit=None, shallow=None,
> > duplicate=None):
> > setattr(new, attr, copy.deepcopy(value, memo))
> >
> > return new
> > +
> > +def get_current_entropy():
> > + with open("/proc/sys/kernel/random/entropy_avail", "r") as fobj:
> > + return int(fobj.readline())
> > --
> > 1.9.3
> >
> > _______________________________________________
> > anaconda-patches mailing list
> > anaconda-patches at lists.fedorahosted.org
> > https://lists.fedorahosted.org/mailman/listinfo/anaconda-patches
> >
>
> 0 is a very good value for minimum entropy.
> I think that a minimum entropy less than zero should
> be treated as an exceptional condition (rather than an
> expected one). I've marked the places where it seems to
> be being treated as expected.
I don't think it is wise to raise exceptions in such places. I mean, it
may reveal some issues in the calling code giving us something obviously
wrong, on the other hand, I like the approach where a valid value means
some request and an invalid value means "I don't care". The question is
if we want to report/log bugs in callers code or not.
--
Vratislav Podzimek
Anaconda Rider | Red Hat, Inc. | Brno - Czech Republic
More information about the anaconda-patches
mailing list