Source code for kiwi.storage.luks_device

# Copyright (c) 2015 SUSE Linux GmbH.  All rights reserved.
#
# This file is part of kiwi.
#
# kiwi is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# kiwi is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with kiwi.  If not, see <http://www.gnu.org/licenses/>
#
import os
import logging
from typing import Optional

# project
from kiwi.utils.temporary import Temporary
from kiwi.command import Command
from kiwi.defaults import Defaults
from kiwi.storage.device_provider import DeviceProvider
from kiwi.storage.mapped_device import MappedDevice

from kiwi.exceptions import (
    KiwiLuksSetupError
)

log = logging.getLogger('kiwi')


[docs] class LuksDevice(DeviceProvider): """ **Implements luks setup on a storage device** :param object storage_provider: Instance of class based on DeviceProvider """ def __init__(self, storage_provider: DeviceProvider) -> None: #: the underlaying device provider self.storage_provider = storage_provider self.luks_device: Optional[str] = None self.luks_keyfile: str = '' self.luks_name = 'luksRoot' self.option_map = { 'sle12': [ '--cipher', 'aes-xts-plain64', '--key-size', '256', '--hash', 'sha1' ] } def __enter__(self): return self
[docs] def get_device(self) -> Optional[MappedDevice]: """ Instance of MappedDevice providing the luks device :return: mapped luks device :rtype: MappedDevice """ if self.luks_device: return MappedDevice( device=self.luks_device, device_provider=self ) return None
[docs] def create_crypto_luks( self, passphrase: str, osname: str = None, options: list = None, keyfile: str = '', randomize: bool = True, root_dir: str = '' ) -> None: """ Create luks device. Please note the passphrase is readable at creation time of this image. Make sure your host system is secure while this process runs :param string passphrase: credentials :param string osname: distribution name to match distribution specific options for cryptsetup :param list options: further cryptsetup options :param string keyfile: file path name file path name which contains an alternative key to unlock the luks device :param string root_dir: root dir path """ if not options: options = [] if osname: if osname in self.option_map: options += self.option_map[osname] else: raise KiwiLuksSetupError( 'no custom option configuration found for OS %s' % osname ) extra_options = [] storage_device = self.storage_provider.get_device() log.info('Creating crypto LUKS on %s', storage_device) if not passphrase: log.warning('Using an empty passphrase for the key setup') if keyfile: self.luks_keyfile = keyfile keyfile_path = os.path.normpath( os.sep.join([root_dir, self.luks_keyfile]) ) LuksDevice.create_random_keyfile(keyfile_path) if randomize: log.info('--> Randomizing...') storage_size_mbytes = self.storage_provider.get_byte_size( storage_device ) / 1048576 Command.run( [ 'dd', 'if=/dev/urandom', 'bs=1M', 'count=%d' % storage_size_mbytes, 'of=%s' % storage_device ] ) log.info('--> Creating LUKS map') if passphrase and passphrase == 'random': # In random mode use the generated keyfile as the only # key to decrypt. This is only secure if the generated # initrd also gets protected, e.g through encryption # like it is done with the secure linux execution on # zSystems passphrase_file = keyfile_path # Do not add an additional keyfile keyfile = '' elif passphrase: # Setup a passphrase file for which the system will # ask for in an interactive dialog passphrase_file_tmp = Temporary().new_file() with open(passphrase_file_tmp.name, 'w') as credentials: credentials.write(passphrase) passphrase_file = passphrase_file_tmp.name else: # Setup an empty passphrase, insecure and only useful # for initial deployment which then applies a process # to secure the image e.g reencrypt passphrase_file_zero = '/dev/zero' extra_options = [ '--keyfile-size', '32' ] passphrase_file = passphrase_file_zero Command.run( [ 'cryptsetup', '-q', '--key-file', passphrase_file ] + options + extra_options + [ 'luksFormat', storage_device ] ) if keyfile: Command.run( [ 'cryptsetup', '--key-file', passphrase_file ] + extra_options + [ 'luksAddKey', storage_device, keyfile_path ] ) Command.run( [ 'cryptsetup', '--key-file', passphrase_file ] + extra_options + [ 'luksOpen', storage_device, self.luks_name ] ) self.luks_device = '/dev/mapper/' + self.luks_name
[docs] def create_crypttab(self, filename: str) -> None: """ Create crypttab, setting the UUID of the storage device :param string filename: file path name """ storage_device = self.storage_provider.get_device() with open(filename, 'w') as crypttab: luks_uuid = self.storage_provider.get_uuid(storage_device) if self.luks_keyfile: crypttab.write( 'luks UUID={0} /{1}{2}'.format( luks_uuid, self.luks_keyfile.lstrip(os.sep), os.linesep ) ) else: crypttab.write( 'luks UUID={0}{1}'.format( luks_uuid, os.linesep ) )
[docs] def is_loop(self) -> bool: """ Check if storage provider is loop based Return loop status from base storage provider :return: True or False :rtype: bool """ return self.storage_provider.is_loop()
[docs] @staticmethod def create_random_keyfile(filename: str) -> None: """ Create keyfile with random data :param string filename: file path name """ with open(filename, 'wb') as keyfile: keyfile.write(os.urandom(Defaults.get_luks_key_length())) os.chmod(filename, 0o600)
def __exit__(self, exc_type, exc_value, traceback): if self.luks_device: try: Command.run( ['cryptsetup', 'luksClose', self.luks_name] ) except Exception as issue: log.error( 'Shutdown of luks map {0}:{1} failed with: {2}'.format( self.luks_name, self.luks_device, issue ) )