%PDF- %PDF-
Direktori : /proc/227033/root/lib/python2.7/site-packages/salt/modules/ |
Current File : //proc/227033/root/lib/python2.7/site-packages/salt/modules/cryptdev.py |
# -*- coding: utf-8 -*- ''' Salt module to manage Unix cryptsetup jobs and the crypttab file .. versionadded:: 2018.3.0 ''' # Import python libraries from __future__ import absolute_import, print_function, unicode_literals import logging import os import re # Import salt libraries import salt.utils.files import salt.utils.platform import salt.utils.stringutils from salt.exceptions import CommandExecutionError # Import 3rd-party libs from salt.ext import six # Set up logger log = logging.getLogger(__name__) # Define the module's virtual name __virtualname__ = 'cryptdev' def __virtual__(): ''' Only load on POSIX-like systems ''' if salt.utils.platform.is_windows(): return (False, 'The cryptdev module cannot be loaded: not a POSIX-like system') return True class _crypttab_entry(object): ''' Utility class for manipulating crypttab entries. Primarily we're parsing, formatting, and comparing lines. Parsing emits dicts expected from crypttab() or raises a ValueError. ''' class ParseError(ValueError): '''Error raised when a line isn't parsible as a crypttab entry''' crypttab_keys = ('name', 'device', 'password', 'options') crypttab_format = '{name: <12} {device: <44} {password: <22} {options}\n' @classmethod def dict_from_line(cls, line, keys=crypttab_keys): if len(keys) != 4: raise ValueError('Invalid key array: {0}'.format(keys)) if line.startswith('#'): raise cls.ParseError('Comment!') comps = line.split() # If there are only three entries, then the options have been omitted. if len(comps) == 3: comps += [''] if len(comps) != 4: raise cls.ParseError('Invalid Entry!') return dict(six.moves.zip(keys, comps)) @classmethod def from_line(cls, *args, **kwargs): return cls(** cls.dict_from_line(*args, **kwargs)) @classmethod def dict_to_line(cls, entry): return cls.crypttab_format.format(**entry) def __str__(self): '''String value, only works for full repr''' return self.dict_to_line(self.criteria) def __repr__(self): '''Always works''' return repr(self.criteria) def pick(self, keys): '''Returns an instance with just those keys''' subset = dict([(key, self.criteria[key]) for key in keys]) return self.__class__(**subset) def __init__(self, **criteria): '''Store non-empty, non-null values to use as filter''' self.criteria = {key: salt.utils.stringutils.to_unicode(value) for key, value in six.iteritems(criteria) if value is not None} @staticmethod def norm_path(path): '''Resolve equivalent paths equivalently''' return os.path.normcase(os.path.normpath(path)) def match(self, line): '''Compare potentially partial criteria against a complete line''' entry = self.dict_from_line(line) for key, value in six.iteritems(self.criteria): if entry[key] != value: return False return True def active(): ''' List existing device-mapper device details. ''' ret = {} # TODO: This command should be extended to collect more information, such as UUID. devices = __salt__['cmd.run_stdout']('dmsetup ls --target crypt') out_regex = re.compile(r'(?P<devname>\w+)\W+\((?P<major>\d+), (?P<minor>\d+)\)') log.debug(devices) for line in devices.split('\n'): match = out_regex.match(line) if match: dev_info = match.groupdict() ret[dev_info['devname']] = dev_info else: log.warning('dmsetup output does not match expected format') return ret def crypttab(config='/etc/crypttab'): ''' List the contents of the crypttab CLI Example: .. code-block:: bash salt '*' cryptdev.crypttab ''' ret = {} if not os.path.isfile(config): return ret with salt.utils.files.fopen(config) as ifile: for line in ifile: line = salt.utils.stringutils.to_unicode(line).rstrip('\n') try: entry = _crypttab_entry.dict_from_line(line) entry['options'] = entry['options'].split(',') # Handle duplicate names by appending `_` while entry['name'] in ret: entry['name'] += '_' ret[entry.pop('name')] = entry except _crypttab_entry.ParseError: pass return ret def rm_crypttab(name, config='/etc/crypttab'): ''' Remove the named mapping from the crypttab. If the described entry does not exist, nothing is changed, but the command succeeds by returning ``'absent'``. If a line is removed, it returns ``'change'``. CLI Example: .. code-block:: bash salt '*' cryptdev.rm_crypttab foo ''' modified = False criteria = _crypttab_entry(name=name) # For each line in the config that does not match the criteria, add it to # the list. At the end, re-create the config from just those lines. lines = [] try: with salt.utils.files.fopen(config, 'r') as ifile: for line in ifile: line = salt.utils.stringutils.to_unicode(line) try: if criteria.match(line): modified = True else: lines.append(line) except _crypttab_entry.ParseError: lines.append(line) except (IOError, OSError) as exc: msg = 'Could not read from {0}: {1}' raise CommandExecutionError(msg.format(config, exc)) if modified: try: with salt.utils.files.fopen(config, 'w+') as ofile: ofile.writelines((salt.utils.stringutils.to_str(line) for line in lines)) except (IOError, OSError) as exc: msg = 'Could not write to {0}: {1}' raise CommandExecutionError(msg.format(config, exc)) # If we reach this point, the changes were successful return 'change' if modified else 'absent' def set_crypttab( name, device, password='none', options='', config='/etc/crypttab', test=False, match_on='name'): ''' Verify that this device is represented in the crypttab, change the device to match the name passed, or add the name if it is not present. CLI Example: .. code-block:: bash salt '*' cryptdev.set_crypttab foo /dev/sdz1 mypassword swap,size=256 ''' # Fix the options type if it is not a string if options is None: options = '' elif isinstance(options, six.string_types): pass elif isinstance(options, list): options = ','.join(options) else: msg = 'options must be a string or list of strings' raise CommandExecutionError(msg) # preserve arguments for updating entry_args = { 'name': name, 'device': device, 'password': password if password is not None else 'none', 'options': options, } lines = [] ret = None # Transform match_on into list--items will be checked later if isinstance(match_on, list): pass elif not isinstance(match_on, six.string_types): msg = 'match_on must be a string or list of strings' raise CommandExecutionError(msg) else: match_on = [match_on] # generate entry and criteria objects, handle invalid keys in match_on entry = _crypttab_entry(**entry_args) try: criteria = entry.pick(match_on) except KeyError: filterFn = lambda key: key not in _crypttab_entry.crypttab_keys invalid_keys = six.moves.filter(filterFn, match_on) msg = 'Unrecognized keys in match_on: "{0}"'.format(invalid_keys) raise CommandExecutionError(msg) # parse file, use ret to cache status if not os.path.isfile(config): raise CommandExecutionError('Bad config file "{0}"'.format(config)) try: with salt.utils.files.fopen(config, 'r') as ifile: for line in ifile: line = salt.utils.stringutils.to_unicode(line) try: if criteria.match(line): # Note: If ret isn't None here, # we've matched multiple lines ret = 'present' if entry.match(line): lines.append(line) else: ret = 'change' lines.append(six.text_type(entry)) else: lines.append(line) except _crypttab_entry.ParseError: lines.append(line) except (IOError, OSError) as exc: msg = 'Couldn\'t read from {0}: {1}' raise CommandExecutionError(msg.format(config, exc)) # add line if not present or changed if ret is None: lines.append(six.text_type(entry)) ret = 'new' if ret != 'present': # ret in ['new', 'change']: if not test: try: with salt.utils.files.fopen(config, 'w+') as ofile: # The line was changed, commit it! ofile.writelines((salt.utils.stringutils.to_str(line) for line in lines)) except (IOError, OSError): msg = 'File not writable {0}' raise CommandExecutionError(msg.format(config)) return ret def open(name, device, keyfile): ''' Open a crypt device using ``cryptsetup``. The ``keyfile`` must not be ``None`` or ``'none'``, because ``cryptsetup`` will otherwise ask for the password interactively. CLI Example: .. code-block:: bash salt '*' cryptdev.open foo /dev/sdz1 /path/to/keyfile ''' if keyfile is None or keyfile == 'none' or keyfile == '-': raise CommandExecutionError('For immediate crypt device mapping, keyfile must not be none') code = __salt__['cmd.retcode']('cryptsetup open --key-file {0} {1} {2}' .format(keyfile, device, name)) return code == 0 def close(name): ''' Close a crypt device using ``cryptsetup``. CLI Example: .. code-block:: bash salt '*' cryptdev.close foo ''' code = __salt__['cmd.retcode']('cryptsetup close {0}'.format(name)) return code == 0