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
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
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
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