コード例 #1
0
def cryptdisks_stop(target, context=None):
    """
    Execute cryptdisks_stop_ or emulate its functionality.

    :param target: The mapped device name (a string).
    :param context: An execution context created by :mod:`executor.contexts`
                    (coerced using :func:`.coerce_context()`).
    :raises: :exc:`~executor.ExternalCommandFailed` when a command fails,
             :exc:`~exceptions.ValueError` when no entry in `/etc/crypttab`_
             matches `target`.

    .. _cryptdisks_stop: https://manpages.debian.org/cryptdisks_stop
    """
    context = coerce_context(context)
    logger.debug("Checking if `cryptdisks_stop' program is installed ..")
    if context.find_program('cryptdisks_stop'):
        logger.debug("Using the real `cryptdisks_stop' program ..")
        context.execute('cryptdisks_stop', target, sudo=True)
    else:
        logger.debug(
            "Emulating `cryptdisks_stop' functionality (program not installed) .."
        )
        for entry in parse_crypttab(context=context):
            if entry.target == target and 'luks' in entry.options:
                logger.debug("Matched /etc/crypttab entry: %s", entry)
                if entry.is_unlocked:
                    lock_filesystem(context=context, target=target)
                else:
                    logger.debug(
                        "Encrypted filesystem is already locked, doing nothing .."
                    )
                break
        else:
            msg = "Encrypted filesystem not listed in /etc/crypttab! (%r)"
            raise ValueError(msg % target)
コード例 #2
0
def generate_key_file(filename, size=DEFAULT_KEY_SIZE, context=None):
    """
    Generate a file with random contents that can be used as a key file.

    :param filename: The pathname of the key file (a string).
    :param size: How large the key file should be (see :func:`.coerce_size()`,
                 defaults to :data:`DEFAULT_KEY_SIZE`).
    :param context: An execution context created by :mod:`executor.contexts`
                    (coerced using :func:`.coerce_context()`).
    :raises: :exc:`~executor.ExternalCommandFailed` when the command fails.
    """
    context = coerce_context(context)
    size = coerce_size(size)
    logger.debug("Creating key file of %i bytes: %s", size, filename)
    context.execute(
        'dd',
        'if=/dev/urandom',
        'of=%s' % filename,
        'bs=%i' % size,
        'count=1',
        # I'd rather use `status=none' then silent=True, however the
        # `status=none' flag isn't supported on Ubuntu 12.04 which
        # currently runs on Travis CI, so there you go :-p.
        silent=True,
        sudo=True,
    )
    context.execute('chown', 'root:root', filename, sudo=True)
    context.execute('chmod', '400', filename, sudo=True)
コード例 #3
0
ファイル: luks.py プロジェクト: JAvito-GC/Linux-Utils
def cryptdisks_start(target, context=None):
    """
    Execute :man:`cryptdisks_start` or emulate its functionality.

    :param target: The mapped device name (a string).
    :param context: See :func:`.coerce_context()` for details.
    :raises: :exc:`~executor.ExternalCommandFailed` when a command fails,
             :exc:`~exceptions.ValueError` when no entry in `/etc/crypttab`_
             matches `target`.
    """
    context = coerce_context(context)
    logger.debug("Checking if `cryptdisks_start' program is installed ..")
    if context.find_program('cryptdisks_start'):
        logger.debug("Using the real `cryptdisks_start' program ..")
        context.execute('cryptdisks_start', target, sudo=True)
    else:
        logger.debug("Emulating `cryptdisks_start' functionality (program not installed) ..")
        for entry in parse_crypttab(context=context):
            if entry.target == target and 'luks' in entry.options:
                logger.debug("Matched /etc/crypttab entry: %s", entry)
                if entry.is_unlocked:
                    logger.debug("Encrypted filesystem is already unlocked, doing nothing ..")
                else:
                    unlock_filesystem(context=context,
                                      device_file=entry.source_device,
                                      key_file=entry.key_file,
                                      options=entry.options,
                                      target=entry.target)
                break
        else:
            msg = "Encrypted filesystem not listed in /etc/crypttab! (%r)"
            raise ValueError(msg % target)
コード例 #4
0
def parse_tab_file(filename, context=None, encoding='UTF-8'):
    """
    Parse a Linux configuration file like ``/etc/fstab`` or ``/etc/crypttab``.

    :param filename: The absolute pathname of the file to parse (a string).
    :param context: See :func:`.coerce_context()` for details.
    :param encoding: The name of the text encoding of the file (a string).
    :returns: A generator of :class:`TabFileEntry` objects.

    This function strips comments (the character ``#`` until the end of
    the line) and splits each line into tokens separated by whitespace.
    """
    context = coerce_context(context)
    contents = context.read_file(filename).decode(encoding)
    for line_number, line in enumerate(contents.splitlines(), start=1):
        # Strip comments.
        line = re.sub('#.*', '', line)
        # Tokenize input.
        tokens = line.split()
        if tokens:
            yield TabFileEntry(
                context=context,
                configuration_file=filename,
                line_number=line_number,
                tokens=tokens,
            )
コード例 #5
0
    def __init__(self, filename, size=DEFAULT_KEY_SIZE, context=None):
        """
        Initialize a :class:`TemporaryKeyFile` object.

        Refer to :func:`generate_key_file()`
        for details about argument handling.
        """
        self.context = coerce_context(context)
        self.filename = filename
        self.size = size
コード例 #6
0
ファイル: luks.py プロジェクト: JAvito-GC/Linux-Utils
def lock_filesystem(target, context=None):
    """
    Lock a currently unlocked LUKS filesystem.

    :param target: The mapped device name (a string).
    :param context: See :func:`.coerce_context()` for details.
    :raises: :exc:`~executor.ExternalCommandFailed` when the command fails.
    """
    context = coerce_context(context)
    logger.debug("Locking filesystem %s ..", target)
    close_command = ['cryptsetup', 'luksClose', target]
    context.execute(*close_command, sudo=True, tty=False)
コード例 #7
0
def unlock_filesystem(device_file,
                      target,
                      key_file=None,
                      options=None,
                      context=None):
    """
    Unlock an encrypted LUKS filesystem.

    :param device_file: The pathname of the block special device or file (a string).
    :param target: The mapped device name (a string).
    :param key_file: The pathname of the key file used to encrypt the
                     filesystem (a string or :data:`None`).
    :param options: An iterable of strings with encryption options or
                    :data:`None` (in which case the default options are used).
                    Currently 'discard', 'readonly' and 'tries' are the only
                    supported options (other options are silently ignored).
    :param context: An execution context created by :mod:`executor.contexts`
                    (coerced using :func:`.coerce_context()`).
    :raises: :exc:`~executor.ExternalCommandFailed` when the command fails.

    If no `key_file` is given the operator is prompted to enter a password.
    """
    context = coerce_context(context)
    logger.debug("Unlocking filesystem %s ..", device_file)
    tries = 3
    open_command = ['cryptsetup']
    open_options = []
    if key_file:
        open_options.append('--key-file=%s' % key_file)
    if options:
        for opt in options:
            if opt == 'discard':
                open_options.append('--allow-discards')
            elif opt == 'readonly':
                open_options.append('--readonly')
            elif opt.startswith('tries='):
                name, _, value = opt.partition('=')
                tries = int(value)
    open_command.extend(sorted(open_options))
    open_command.extend(['luksOpen', device_file, target])
    for attempt in retry_limit(tries):
        try:
            context.execute(*open_command, sudo=True, tty=(key_file is None))
        except ExternalCommandFailed:
            if attempt < tries and not key_file:
                logger.warning("Failed to unlock, retrying ..")
            else:
                raise
        else:
            break
コード例 #8
0
ファイル: luks.py プロジェクト: JAvito-GC/Linux-Utils
def create_image_file(filename, size, context=None):
    r"""
    Create an image file filled with bytes containing zero (``\0``).

    :param filename: The pathname of the image file (a string).
    :param size: How large the image file should be (see :func:`.coerce_size()`).
    :param context: See :func:`.coerce_context()` for details.
    :raises: :exc:`~exceptions.ValueError` when `size` is invalid,
             :exc:`~executor.ExternalCommandFailed` when the command fails.
    """
    context = coerce_context(context)
    size = coerce_size(size)
    logger.debug("Creating image file of %i bytes: %s", size, filename)
    head_command = 'head --bytes=%i /dev/zero > %s'
    context.execute(head_command % (size, quote(filename)), shell=True, tty=False)
コード例 #9
0
def find_gateway_mac(context=None):
    """
    Find the MAC address of the current gateway using :func:`find_gateway_ip()` and :func:`find_mac_address()`.

    :param context: See :func:`.coerce_context()` for details.
    :returns: The MAC address of the gateway (a string) or :data:`None`.
    """
    context = coerce_context(context)
    ip_address = find_gateway_ip(context)
    if ip_address:
        mac_address = find_mac_address(ip_address, context)
        if mac_address:
            logger.debug("Found gateway MAC address: %s", mac_address)
            return mac_address
    logger.debug("Couldn't find MAC address of gateway in 'arp -n' output!")
コード例 #10
0
def determine_network_location(context=None, **gateways):
    """
    Determine the physical location of the current system.

    This works by matching the MAC address of the current gateway against a set
    of known MAC addresses, which provides a simple but robust way to identify
    the current network. Because networks tend to have a physical location,
    identifying the current network tells us our physical location.

    :param gateways: One or more keyword arguments with lists of strings
                     containing MAC addresses of known networks.
    :param context: See :func:`.coerce_context()` for details.
    :returns: The name of the matched MAC address (a string) or :data:`None`
              when the MAC address of the current gateway is unknown.

    Here's an example involving two networks and a physical location with
    multiple gateways:

    .. code-block:: python

       >>> determine_network_location(
       ...    home=['84:9C:A6:76:23:8E'],
       ...    office=['00:15:C5:5F:92:79', 'B6:25:B2:19:28:61'],
       ... )
       'home'

    This is used to tweak my desktop environment based on the physical location
    of my laptop, for example at home my external monitor is to the right of my
    laptop whereas at work it's the other way around, so the :man:`xrandr`
    commands to be run differ between the two locations.
    """
    context = coerce_context(context)
    current_gateway_mac = find_gateway_mac(context)
    if current_gateway_mac:
        for network_name, known_gateways in gateways.items():
            if any(current_gateway_mac.upper() == gateway.upper()
                   for gateway in known_gateways):
                logger.info("%s is connected to the %s network.", context,
                            network_name)
                return network_name
        logger.info(
            "%s isn't connected to a known network (unknown gateway MAC address %s).",
            context, current_gateway_mac)
    else:
        logger.info(
            "Failed to determine gateway of %s, assuming network connection is down.",
            context)
コード例 #11
0
def parse_crypttab(filename='/etc/crypttab', context=None):
    """
    Parse the Debian Linux configuration file ``/etc/crypttab``.

    :param filename: The absolute pathname of the file to parse (a string,
                     defaults to ``/etc/crypttab``).
    :param context: See :func:`.coerce_context()` for details.
    :returns: A generator of :class:`EncryptedFileSystemEntry` objects.

    Here's an example:

    >>> from linux_utils.crypttab import parse_crypttab
    >>> print(next(parse_crypttab()))
    EncryptedFileSystemEntry(
        configuration_file='/etc/crypttab',
        line_number=3,
        target='ssd',
        source='UUID=31678141-3931-4683-a4d2-09eadec81d01',
        source_device='/dev/disk/by-uuid/31678141-3931-4683-a4d2-09eadec81d01',
        key_file='none',
        options=['luks', 'discard'],
    )

    .. versionchanged:: 0.6
       It is not an error when `filename` doesn't exist, because of my
       experience that ``/etc/crypttab`` doesn't exist in default Debian and
       Ubuntu installations (unless that system was specifically set up with
       root disk encryption using the installation wizard). This used to raise
       an exception, but this was changed in `release 0.6`_.

    .. _release 0.6: https://linux-utils.readthedocs.io/changelog.html#release-0-6-2018-07-03
    """
    context = coerce_context(context)
    if context.is_file(filename):
        for entry in parse_tab_file(filename=filename, context=context):
            if len(entry.tokens) >= 4:
                # Transform the object into our type.
                entry.__class__ = EncryptedFileSystemEntry
                yield entry
            elif len(entry.tokens) > 0:
                logger.warning(
                    "Ignoring line %i in %s because I couldn't parse it!",
                    entry.line_number, entry.configuration_file)
    else:
        logger.notice(
            "No encrypted filesystems found on %s because %s doesn't exist.",
            context, filename)
コード例 #12
0
def find_gateway_ip(context=None):
    """
    Find the IP address of the current gateway using the ``ip route show`` command.

    :param context: See :func:`.coerce_context()` for details.
    :returns: The IP address of the gateway (a string) or :data:`None`.
    """
    context = coerce_context(context)
    logger.debug("Looking for IP address of current gateway ..")
    for line in context.capture("ip", "route", "show").splitlines():
        tokens = line.split()
        logger.debug("Parsing 'ip route show' output: %s", tokens)
        if len(tokens) >= 3 and tokens[:2] == ["default", "via"]:
            ip_address = tokens[2]
            logger.debug("Found gateway IP address: %s", ip_address)
            return ip_address
    logger.debug(
        "Couldn't find IP address of gateway in 'ip route show' output!")
コード例 #13
0
def find_mac_address(ip_address, context=None):
    """
    Determine the MAC address of an IP address using the ``arp -n`` command.

    :param ip_address: The IP address we're interested in (a string).
    :param context: See :func:`.coerce_context()` for details.
    :returns: The MAC address of the IP address (a string) or :data:`None`.
    """
    context = coerce_context(context)
    logger.debug("Looking for MAC address of %s ..", ip_address)
    for line in context.capture("arp", "-n").splitlines():
        tokens = line.split()
        logger.debug("Parsing 'arp -n' output: %s", tokens)
        if len(tokens) >= 3 and tokens[0] == ip_address:
            mac_address = tokens[2]
            logger.debug("Found MAC address of %s: %s", ip_address,
                         mac_address)
            return mac_address
    logger.debug("Couldn't find MAC address in 'arp -n' output!")
コード例 #14
0
ファイル: luks.py プロジェクト: JAvito-GC/Linux-Utils
def create_encrypted_filesystem(device_file, key_file=None, context=None):
    """
    Create an encrypted LUKS filesystem.

    :param device_file: The pathname of the block special device or file (a string).
    :param key_file: The pathname of the key file used to encrypt the
                     filesystem (a string or :data:`None`).
    :param context: See :func:`.coerce_context()` for details.
    :raises: :exc:`~executor.ExternalCommandFailed` when the command fails.

    If no `key_file` is given the operator is prompted to choose a password.
    """
    context = coerce_context(context)
    logger.debug("Creating encrypted filesystem on %s ..", device_file)
    format_command = ['cryptsetup']
    if key_file:
        format_command.append('--batch-mode')
    format_command.append('luksFormat')
    format_command.append(device_file)
    if key_file:
        format_command.append(key_file)
    context.execute(*format_command, sudo=True, tty=(key_file is None))
コード例 #15
0
def have_internet_connection(endpoint="8.8.8.8", context=None):
    """
    Check if an internet connection is available using :man:`ping`.

    :param endpoint: The public IP address to :man:`ping` (a string).
    :param context: See :func:`.coerce_context()` for details.
    :returns: :data:`True` if an internet connection is available,
              :data:`False` otherwise.

    This works by pinging 8.8.8.8 which is one of `Google public DNS servers`_.
    This IP address was chosen because it is documented that Google uses
    anycast_ to keep this IP address available at all times.

    .. _Google public DNS servers: https://developers.google.com/speed/public-dns/
    .. _anycast: https://en.wikipedia.org/wiki/Anycast
    """
    context = coerce_context(context)
    logger.debug("Checking if %s has internet connectivity ..", context)
    if context.test("ping", "-c1", "-w1", "8.8.8.8"):
        logger.debug("Confirmed that %s has internet connectivity.", context)
        return True
    else:
        logger.debug("No internet connectivity detected on %s.", context)
        return False
コード例 #16
0
ファイル: tests.py プロジェクト: JAvito-GC/Linux-Utils
 def test_coerce_context(self):
     """Test coercion of execution contexts."""
     assert isinstance(coerce_context(None), LocalContext)
     instance = LocalContext()
     assert coerce_context(instance) is instance
     self.assertRaises(ValueError, coerce_context, 1)