Exemple #1
0
def get_unlocked_luks_containers_uuids():
    """
    Returns a list of LUKS container uuids backing open LUKS volumes. 
    The method used is to first run:
    'dmsetup info --columns --noheadings -o name --target crypt' eg output:
    luks-82fd9db1-e1c1-488d-9b42-536d0a82caeb
    luks-3efb3830-fee1-4a9e-a5c6-ea456bfc269e
    luks-a47f4950-3296-4504-b9a4-2dc75681a6ad
    to get a list of open LUKS containers (--target crypt). If the usual naming 
    convention is followed we have a name format of luks-<uuid> with len = 41
    and we can extract the uuid of the LUKS container from it syntactically.
    If this naming convention is not matched then we fail over to calling:
    get_open_luks_container_dev() and then looking up that devices uuid via
    our uuid_name_map dictionary.
    :return: list containing the uuids of LUKS containers that have currently
    open volumes, or empty list if none open or an error occurred. 
    """
    open_luks_container_uuids = []
    # flag to minimise calls to get_uuid_name_map()
    uuid_name_map_retrieved = False
    uuid_name_map = {}
    out, err, rc = run_command([
        DMSETUP,
        "info",
        "--columns",
        "--noheadings",
        "--options",
        "name",
        "--target",
        "crypt",
    ])
    if len(out) > 0 and rc == 0:
        # The output has at least one line and our dmsetup executed OK.
        for each_line in out:
            if each_line == "":
                continue
            backing_container_uuid = None
            if len(each_line) == 41 and re.match("luks-", each_line):
                # good chance on "luks-a47f4950-3296-4504-b9a4-2dc75681a6ad"
                # naming convention so strip uuid from this (cheap and quick)
                backing_container_uuid = each_line[5:]
            else:
                # More expensive two step process to retrieve uuid of LUKS
                # container backing this open LUKS volume.
                # Initial call to gain backing device name for our container
                container_dev = get_open_luks_container_dev(each_line)
                # strip leading /dev/ from device name if any returned.
                if container_dev is not "":
                    container_dev = container_dev.split("/")[-1]
                    # should now have name without path ie 'vdd' ready to
                    # index our uuid_name_map.
                    if not uuid_name_map_retrieved:
                        uuid_name_map = get_uuid_name_map()
                        uuid_name_map_retrieved = True
                    # second stage where we look up this devices uuid
                    backing_container_uuid = uuid_name_map[container_dev]
            # if a backing container uuid was found add it to our list
            if backing_container_uuid is not None:
                open_luks_container_uuids.append(backing_container_uuid)
    return open_luks_container_uuids
Exemple #2
0
def get_unlocked_luks_containers_uuids():
    """
    Returns a list of LUKS container uuids backing open LUKS volumes. 
    The method used is to first run:
    'dmsetup info --columns --noheadings -o name --target crypt' eg output:
    luks-82fd9db1-e1c1-488d-9b42-536d0a82caeb
    luks-3efb3830-fee1-4a9e-a5c6-ea456bfc269e
    luks-a47f4950-3296-4504-b9a4-2dc75681a6ad
    to get a list of open LUKS containers (--target crypt). If the usual naming 
    convention is followed we have a name format of luks-<uuid> with len = 41
    and we can extract the uuid of the LUKS container from it syntactically.
    If this naming convention is not matched then we fail over to calling:
    get_open_luks_container_dev() and then looking up that devices uuid via
    our uuid_name_map dictionary.
    :return: list containing the uuids of LUKS containers that have currently
    open volumes, or empty list if none open or an error occurred. 
    """
    open_luks_container_uuids = []
    # flag to minimise calls to get_uuid_name_map()
    uuid_name_map_retrieved = False
    uuid_name_map = {}
    out, err, rc = run_command([DMSETUP, 'info', '--columns', '--noheadings',
                                '--options', 'name', '--target', 'crypt'])
    if len(out) > 0 and rc == 0:
        # The output has at least one line and our dmsetup executed OK.
        for each_line in out:
            if each_line == '':
                continue
            backing_container_uuid = None
            if len(each_line) == 41 and re.match('luks-', each_line):
                # good chance on "luks-a47f4950-3296-4504-b9a4-2dc75681a6ad"
                # naming convention so strip uuid from this (cheap and quick)
                backing_container_uuid = each_line[5:]
            else:
                # More expensive two step process to retrieve uuid of LUKS
                # container backing this open LUKS volume.
                # Initial call to gain backing device name for our container
                container_dev = get_open_luks_container_dev(each_line)
                # strip leading /dev/ from device name if any returned.
                if container_dev is not '':
                    container_dev = container_dev.split('/')[-1]
                    # should now have name without path ie 'vdd' ready to
                    # index our uuid_name_map.
                    if not uuid_name_map_retrieved:
                        uuid_name_map = get_uuid_name_map()
                        uuid_name_map_retrieved = True
                    # second stage where we look up this devices uuid
                    backing_container_uuid = uuid_name_map[container_dev]
            # if a backing container uuid was found add it to our list
            if backing_container_uuid is not None:
                open_luks_container_uuids.append(backing_container_uuid)
    return open_luks_container_uuids
Exemple #3
0
def update_crypttab(uuid, keyfile_entry):
    """
    If no existing /etc/crypttab we call a simplified function specific to new 
    single entry crypttab creation: new_crypttab_single_entry(), otherwise we 
    read the existing crypttab file and replace, wipe, or create a relevant 
    entry for our passed device by uuid info. All commented entries are 
    removed, as are entries deemed non valid. New entries are of a single
    format:
    luks-<uuid> UUID=<uuid> /root/keyfile-<uuid> luks
    N.B. Care is taken to ensure our secure temporary file containing our
    crypttab line details is removed irrespective of outcome.
    :param uuid: uuid of the associated LUKS container such as is returned by:
    cryptsetup luksUUID <LUKS-container-dev>
    :param keyfile_entry: the literal intended contents of the 3rd column.
    :return: False or exception raised if crypttab edit failed or no uuid 
    passed, True otherwise.
    """
    # Deal elegantly with null or '' uuid
    if (uuid is None) or uuid == '':
        return False
    uuid_name_map_retrieved = False
    # Simpler paths for when no /etc/crypttab file exists.
    if not os.path.isfile(CRYPTTABFILE):
        if keyfile_entry == 'false':
            # The string 'false' is used to denote the removal of an existing
            # entry so we are essentially done as by whatever means there are
            # no entries in a non-existent crypttab.
            return True
        # We have no existing cryptab but a pending non 'false' entry.
        # Call specialized single entry crypttab creation method.
        return new_crypttab_single_entry(uuid, keyfile_entry)
    # By now we have an existing /etc/crypttab so we open it in readonly and
    # 'on the fly' edit line by line into a secure temp file.
    tfo, npath = mkstemp()
    # Pythons _candidate_tempdir_list() should ensure our npath temp file is
    # in memory (tmpfs). From https://docs.python.org/2/library/tempfile.html
    # we have "Creates a temporary file in the most secure manner possible."
    with open(CRYPTTABFILE, 'r') as ct_original, open(npath, 'w') as temp_file:
        # examine original crypttab line by line.
        new_entry = None  # temp var that doubles as flag for entry made.
        for line in ct_original.readlines():  # readlines (whole file in one).
            update_line = False
            if line == '\n' or re.match(line, '#') is not None:
                # blank line (return) or remark line, strip for simplicity.
                continue
            line_fields = line.split()
            # sanitize remaining lines, bare minimum count of entries eg:
            # mapper-name source-dev
            # however 3 is a more modern minimum so drop < 3 column entries.
            if len(line_fields) < 3:
                continue
            # We have a viable line of at least 3 columns so entertain it.
            # Interpret the source device entry in second column (index 1)
            if re.match('UUID=', line_fields[1]) is not None:
                # we have our native UUID reference so split and compare
                source_dev_fields = line_fields[1].split('=')
                if len(source_dev_fields) is not 2:
                    # ie "UUID=" with no value which is non legit so skip
                    continue
                # we should have a UUID=<something> entry so examine it
                if source_dev_fields[1] == uuid:
                    # Matching source device uuid entry so set flag
                    update_line = True
                else:
                    # no UUID= type entry found so check for dev name
                    # eg instead of 'UUID=<uuid>' we have eg: '/dev/sdd'
                    if re.match('/dev', source_dev_fields[1]) is not None:
                        # We have a dev entry so strip the path.
                        dev_no_path = source_dev_fields[1].split('/')[-1]
                        # index our uuid_name_map for dev name comparison
                        if not uuid_name_map_retrieved:
                            uuid_name_map = get_uuid_name_map()
                            uuid_name_map_retrieved = True
                        uuid_of_source = uuid_name_map[dev_no_path]
                        if uuid_of_source == uuid:
                            # we have a non native /dev type entry but
                            # the uuid's match so replace with quicker
                            # native form of luks-<uuid> UUID=<uuid> etc
                            update_line = True
            if update_line:
                # We have a device match by uuid with an existing line.
                if keyfile_entry == 'false':
                    # The string 'false' is used to denote no crypttab entry,
                    # this we can do by simply skipping this line.
                    continue
                # Update the line with our native format but try and
                # preserve custom options in column 4 if they exist:
                # Use new mapper name (potentially controversial).
                if len(line_fields) > 3:
                    new_entry = (
                        'luks-%s UUID=%s %s %s\n' %
                        (uuid, uuid, keyfile_entry, ' '.join(line_fields[3:])))
                else:
                    # we must have a 3 column entry (>= 3 and then > 3)
                    # N.B. later 'man crypttab' suggests 4 columns as
                    # mandatory but that was not observed. We add 'luks'
                    # as fourth column entry just in case.
                    new_entry = ('luks-%s UUID=%s %s luks\n' %
                                 (uuid, uuid, keyfile_entry))
                temp_file.write(new_entry)
            else:
                # No update flag and no original line skip so we
                # simply copy over what ever line we found. Most likely a non
                # matching device.
                temp_file.write(line)
        if keyfile_entry != 'false' and new_entry is None:
            # We have scanned the existing crypttab and not yet made our edit.
            # The string 'false' is used to denote no crypttab entry and if
            # new_entry is still None we have made no edit.
            new_entry = ('luks-%s UUID=%s %s luks\n' %
                         (uuid, uuid, keyfile_entry))
            temp_file.write(new_entry)
    # secure temp file now holds our proposed (post edit) crypttab.
    # Copy contents over existing crypttab and ensure tempfile is removed.
    try:
        # shutil.copy2 is equivalent to cp -p (preserver attributes).
        # This preserves the secure defaults of the temp file without having
        # to chmod there after. Result is the desired:
        # -rw------- 1 root root
        # ie rw to root only or 0600
        # and avoiding a window prior to a separate chmod command.
        shutil.copy2(npath, CRYPTTABFILE)
    except Exception as e:
        msg = ('Exception while creating fresh %s: %s' %
               (CRYPTTABFILE, e.__str__()))
        raise Exception(msg)
    finally:
        if os.path.exists(npath):
            try:
                os.remove(npath)
            except Exception as e:
                msg = ('Exception while removing temp file %s: %s' %
                       (npath, e.__str__()))
                raise Exception(msg)
    return True
Exemple #4
0
def update_crypttab(uuid, keyfile_entry):
    """
    If no existing /etc/crypttab we call a simplified function specific to new 
    single entry crypttab creation: new_crypttab_single_entry(), otherwise we 
    read the existing crypttab file and replace, wipe, or create a relevant 
    entry for our passed device by uuid info. All commented entries are 
    removed, as are entries deemed non valid. New entries are of a single
    format:
    luks-<uuid> UUID=<uuid> /root/keyfile-<uuid> luks
    N.B. Care is taken to ensure our secure temporary file containing our
    crypttab line details is removed irrespective of outcome.
    :param uuid: uuid of the associated LUKS container such as is returned by:
    cryptsetup luksUUID <LUKS-container-dev>
    :param keyfile_entry: the literal intended contents of the 3rd column.
    :return: False or exception raised if crypttab edit failed or no uuid 
    passed, True otherwise.
    """
    # Deal elegantly with null or '' uuid
    if (uuid is None) or uuid == '':
        return False
    uuid_name_map_retrieved = False
    # Simpler paths for when no /etc/crypttab file exists.
    if not os.path.isfile(CRYPTTABFILE):
        if keyfile_entry == 'false':
            # The string 'false' is used to denote the removal of an existing
            # entry so we are essentially done as by whatever means there are
            # no entries in a non-existent crypttab.
            return True
        # We have no existing cryptab but a pending non 'false' entry.
        # Call specialized single entry crypttab creation method.
        return new_crypttab_single_entry(uuid, keyfile_entry)
    # By now we have an existing /etc/crypttab so we open it in readonly and
    # 'on the fly' edit line by line into a secure temp file.
    tfo, npath = mkstemp()
    # Pythons _candidate_tempdir_list() should ensure our npath temp file is
    # in memory (tmpfs). From https://docs.python.org/2/library/tempfile.html
    # we have "Creates a temporary file in the most secure manner possible."
    with open(CRYPTTABFILE, 'r') as ct_original, open(npath, 'w') as temp_file:
        # examine original crypttab line by line.
        new_entry = None # temp var that doubles as flag for entry made.
        for line in ct_original.readlines():  # readlines (whole file in one).
            update_line = False
            if line == '\n' or re.match(line, '#') is not None:
                # blank line (return) or remark line, strip for simplicity.
                continue
            line_fields = line.split()
            # sanitize remaining lines, bare minimum count of entries eg:
            # mapper-name source-dev
            # however 3 is a more modern minimum so drop < 3 column entries.
            if len(line_fields) < 3:
                continue
            # We have a viable line of at least 3 columns so entertain it.
            # Interpret the source device entry in second column (index 1)
            if re.match('UUID=', line_fields[1]) is not None:
                # we have our native UUID reference so split and compare
                source_dev_fields = line_fields[1].split('=')
                if len(source_dev_fields) is not 2:
                    # ie "UUID=" with no value which is non legit so skip
                    continue
                # we should have a UUID=<something> entry so examine it
                if source_dev_fields[1] == uuid:
                    # Matching source device uuid entry so set flag
                    update_line = True
                else:
                    # no UUID= type entry found so check for dev name
                    # eg instead of 'UUID=<uuid>' we have eg: '/dev/sdd'
                    if re.match('/dev', source_dev_fields[1]) is not None:
                        # We have a dev entry so strip the path.
                        dev_no_path = source_dev_fields[1].split('/')[-1]
                        # index our uuid_name_map for dev name comparison
                        if not uuid_name_map_retrieved:
                            uuid_name_map = get_uuid_name_map()
                            uuid_name_map_retrieved = True
                        uuid_of_source = uuid_name_map[dev_no_path]
                        if uuid_of_source == uuid:
                            # we have a non native /dev type entry but
                            # the uuid's match so replace with quicker
                            # native form of luks-<uuid> UUID=<uuid> etc
                            update_line = True
            if update_line:
                # We have a device match by uuid with an existing line.
                if keyfile_entry == 'false':
                    # The string 'false' is used to denote no crypttab entry,
                    # this we can do by simply skipping this line.
                    continue
                # Update the line with our native format but try and
                # preserve custom options in column 4 if they exist:
                # Use new mapper name (potentially controversial).
                if len(line_fields) > 3:
                    new_entry = ('luks-%s UUID=%s %s %s\n' %
                                 (uuid, uuid, keyfile_entry,
                                  ' '.join(line_fields[3:])))
                else:
                    # we must have a 3 column entry (>= 3 and then > 3)
                    # N.B. later 'man crypttab' suggests 4 columns as
                    # mandatory but that was not observed. We add 'luks'
                    # as fourth column entry just in case.
                    new_entry = ('luks-%s UUID=%s %s luks\n' % (uuid, uuid,
                                                                keyfile_entry))
                temp_file.write(new_entry)
            else:
                # No update flag and no original line skip so we
                # simply copy over what ever line we found. Most likely a non
                # matching device.
                temp_file.write(line)
        if keyfile_entry != 'false' and new_entry is None:
            # We have scanned the existing crypttab and not yet made our edit.
            # The string 'false' is used to denote no crypttab entry and if
            # new_entry is still None we have made no edit.
            new_entry = ('luks-%s UUID=%s %s luks\n' % (uuid, uuid,
                                                        keyfile_entry))
            temp_file.write(new_entry)
    # secure temp file now holds our proposed (post edit) crypttab.
    # Copy contents over existing crypttab and ensure tempfile is removed.
    try:
        # shutil.copy2 is equivalent to cp -p (preserver attributes).
        # This preserves the secure defaults of the temp file without having
        # to chmod there after. Result is the desired:
        # -rw------- 1 root root
        # ie rw to root only or 0600
        # and avoiding a window prior to a separate chmod command.
        shutil.copy2(npath, CRYPTTABFILE)
    except Exception as e:
        msg = ('Exception while creating fresh %s: %s' % (CRYPTTABFILE,
                                                          e.__str__()))
        raise Exception(msg)
    finally:
        if os.path.exists(npath):
            try:
                os.remove(npath)
            except Exception as e:
                msg = ('Exception while removing temp file %s: %s' %
                       (npath, e.__str__()))
                raise Exception(msg)
    return True