Exemple #1
0
def stop_unit(unit_name):
    state = get_unit_state(unit_name)
    if state['ActiveState'] == 'stopped':
        return Unchanged(msg='{} already stopped'.format(unit_name))

    proc.run([config['cmd_systemctl'], 'stop', unit_name])
    return Changed(msg='Stopped {}'.format(unit_name))
Exemple #2
0
    def create(self, python='python3', global_site_packages=False):
        # FIXME: check if python version and global_site_packages are correct,
        #        and correct / recreat (--clear?) otherwise

        if not remote.stat(self.python):
            args = [
                config['cmd_venv'],
                '-p',
                python,
            ]

            if global_site_packages:
                args.append('--system-site-packages')
            else:
                args.append('--no-site-packages')

            args.append(self.remote_path)

            proc.run(args)

            return Changed(
                msg='Initialized virtualenv in {}'.format(self.remote_path))

        return Unchanged(msg='virtualenv at {} already initialized'.format(
            self.remote_path))
Exemple #3
0
def reload_unit(unit_name, only_if_running=False):
    if only_if_running:
        cmd = 'reload-or-try-restart'
    else:
        cmd = 'reload-or-restart'
    proc.run([config['cmd_systemctl'], cmd, unit_name])
    return Changed(msg='Reloaded {}'.format(unit_name))
Exemple #4
0
def remove_packages(pkgs, check_first=True, purge=False, max_age=3600):
    if check_first and not set(pkgs).intersection(
            set(info_installed_packages().keys())):
        return Unchanged(msg='Not installed: {}'.format(' '.join(pkgs)))

    update(max_age)

    args = [config['cmd_apt_get']]

    args.extend([
        'remove' if not purge else 'purge',
        '--quiet',
        '--yes',  # FIXME: options below don't work. why?
        # '--option', 'Dpkg::Options::="--force-confdef"',
        # '--option', 'Dpkg::Options::="--force-confold"'
    ])
    args.extend(pkgs)
    proc.run(
        args, extra_env={
            'DEBIAN_FRONTEND': 'noninteractive',
        })

    info_installed_packages.invalidate_cache()

    return Changed(msg='{} {}'.format('Removed' if not purge else 'Purged',
                                      ' '.join(pkgs)))
Exemple #5
0
def disable_unit(unit_name):
    state = get_unit_state(unit_name)
    if state.get('UnitFileState') == 'disabled':
        return Unchanged(msg='{} already disabled'.format(unit_name))

    proc.run([config['cmd_systemctl'], 'disable', unit_name])
    return Changed(msg='Disabled {}'.format(unit_name))
Exemple #6
0
def disable_unit(unit_name):
    state = get_unit_state(unit_name)
    if state.get('UnitFileState') == 'disabled':
        return Unchanged(msg='{} already disabled'.format(unit_name))

    proc.run([config['cmd_systemctl'], 'disable', unit_name])
    return Changed(msg='Disabled {}'.format(unit_name))
Exemple #7
0
def start_unit(unit_name):
    state = get_unit_state(unit_name)
    if 'ActiveState' in state and state['ActiveState'] == 'active' and state['SubState'] == 'running':
        return Unchanged(msg='{} already running'.format(unit_name))

    proc.run([config['cmd_systemctl'], 'start', unit_name])
    return Changed(msg='Started {}'.format(unit_name))
Exemple #8
0
def install_packages(pkgs,
                     check_first=True,
                     release=None,
                     max_age=3600,
                     force=False):
    if check_first and set(pkgs) < set(info_installed_packages().keys()):
        return Unchanged(msg='Already installed: {}'.format(' '.join(pkgs)))

    update(max_age)

    args = [config['cmd_apt_get']]
    if release:
        args.extend(['-t', release])

    args.extend([
        'install',
        '--quiet',
        '--yes',  # FIXME: options below don't work. why?
        # '--option', 'Dpkg::Options::="--force-confdef"',
        # '--option', 'Dpkg::Options::="--force-confold"'
    ])
    if force:
        args.append('--force-yes')
    args.extend(pkgs)
    proc.run(args, extra_env={'DEBIAN_FRONTEND': 'noninteractive', })

    # FIXME: make this a decorator for info, add "change_invalides" decorator?
    info_installed_packages.invalidate_cache()

    # FIXME: detect if packages were installed?
    return Changed(msg='Installed {}'.format(' '.join(pkgs)))
Exemple #9
0
def upgrade(max_age=3600, force=False, dist_upgrade=False):
    # FIXME: should allow upgrading selected packages
    update(max_age)

    args = [config['cmd_apt_get']]

    # FIXME: check for upgrades first and output proper changed status
    args.extend([
        'upgrade' if not dist_upgrade else 'dist-upgrade',
        '--quiet',
        '--yes',
        # FIXME: options below don't work. why?
        # '--option', 'Dpkg::Options::="--force-confdef"',
        # '--option', 'Dpkg::Options::="--force-confold"'
    ])
    if force:
        args.append('--force-yes')
    proc.run(
        args, extra_env={
            'DEBIAN_FRONTEND': 'noninteractive',
        })

    info_installed_packages.invalidate_cache()

    return Changed(msg='Upgraded all packages')
Exemple #10
0
def stop_unit(unit_name):
    state = get_unit_state(unit_name)
    if state['ActiveState'] == 'stopped':
        return Unchanged(msg='{} already stopped'.format(unit_name))

    proc.run([config['cmd_systemctl'], 'stop', unit_name])
    return Changed(msg='Stopped {}'.format(unit_name))
Exemple #11
0
def dpkg_install(paths, check=True):
    if not hasattr(paths, 'keys'):
        pkgs = {}

        # determine package names from filenames. ideally, we would open the
        # package here and check
        for p in paths:
            fn = os.path.basename(p)
            try:
                name, version, tail = fn.split('_', 3)
                pkgs[(name, version)] = p
            except ValueError:
                raise ValueError(
                    'Could not determine package version from '
                    'package filename {}. Please rename the .deb '
                    'to standard debian convention '
                    '(name_version_arch.deb) or supply a specific '
                    'version by passing a dictionary parameter.'.format(fn))

    # log names
    log.debug('Package names: ' + ', '.join('{} -> {}'.format(k, v)
                                            for k, v in pkgs.items()))

    if check:
        missing = []
        installed = info_installed_packages()

        for name, version in pkgs:
            if name not in installed or not installed[name].eq_version(version):
                missing.append((name, version))
    else:
        missing = pkgs.keys()

    log.debug('Installing packages: {}'.format(missing))

    if not missing:
        return Unchanged('Packages {!r} already installed'.format(pkgs.keys()))

    # FIXME: see above
    info_installed_packages.invalidate_cache()

    with fs.remote_tmpdir() as rtmp:
        # upload packages to be installed
        pkg_files = []
        for idx, key in enumerate(missing):
            tmpdest = remote.path.join(rtmp, str(idx) + '.deb')
            fs.upload_file(pkgs[key], tmpdest)
            pkg_files.append(tmpdest)

        # install in a single dpkg install line
        # FIXME: add debconf default and such (same as apt)
        args = [config['cmd_dpkg'], '-i']
        args.extend(pkg_files)
        proc.run(
            args, extra_env={
                'DEBIAN_FRONTEND': 'noninteractive',
            })

    return Changed(msg='Installed packages {!r}'.format(missing))
Exemple #12
0
def start_unit(unit_name):
    state = get_unit_state(unit_name)
    if 'ActiveState' in state and state['ActiveState'] == 'active' and state[
            'SubState'] == 'running':
        return Unchanged(msg='{} already running'.format(unit_name))

    proc.run([config['cmd_systemctl'], 'start', unit_name])
    return Changed(msg='Started {}'.format(unit_name))
Exemple #13
0
def dpkg_install(paths, check=True):
    pkgs = paths
    if not hasattr(paths, 'keys'):
        pkgs = {}

        # determine package names from filenames. ideally, we would open the
        # package here and check
        for p in paths:
            fn = os.path.basename(p)
            try:
                name, version, tail = fn.split('_', 3)
                pkgs[(name, version)] = p
            except ValueError:
                raise ValueError(
                    'Could not determine package version from '
                    'package filename {}. Please rename the .deb '
                    'to standard debian convention '
                    '(name_version_arch.deb) or supply a specific '
                    'version by passing a dictionary parameter.'.format(fn))

    # log names
    log.debug('Package names: ' + ', '.join('{} -> {}'.format(k, v)
                                            for k, v in pkgs.items()))

    if check:
        missing = []
        installed = info_installed_packages()

        for name, version in pkgs:
            if name not in installed or not installed[name].eq_version(
                    version):
                missing.append((name, version))
    else:
        missing = pkgs.keys()

    log.debug('Installing packages: {}'.format(missing))

    if not missing:
        return Unchanged('Packages {!r} already installed'.format(pkgs.keys()))

    # FIXME: see above
    info_installed_packages.invalidate_cache()

    with fs.remote_tmpdir() as rtmp:
        # upload packages to be installed
        pkg_files = []
        for idx, key in enumerate(missing):
            tmpdest = remote.path.join(rtmp, str(idx) + '.deb')
            fs.upload_file(pkgs[key], tmpdest)
            pkg_files.append(tmpdest)

        # install in a single dpkg install line
        # FIXME: add debconf default and such (same as apt)
        args = [config['cmd_dpkg'], '-i']
        args.extend(pkg_files)
        proc.run(args, extra_env={'DEBIAN_FRONTEND': 'noninteractive', })

    return Changed(msg='Installed packages {!r}'.format(missing))
Exemple #14
0
    def install_requirements(self, requirements_txt, upgrade=False):
        # FIXME: collect changes, via freeze?

        args = [self.pip, 'install', '-r', requirements_txt]
        if upgrade:
            args.append('-U')
        proc.run(args)

        return Changed(msg='Installed requirements from {} into {}'.format(
            requirements_txt, self.remote_path))
Exemple #15
0
    def install_requirements(self, requirements_txt, upgrade=False):
        # FIXME: collect changes, via freeze?

        args = [self.pip, 'install', '-r', requirements_txt]
        if upgrade:
            args.append('-U')
        proc.run(args)

        return Changed(msg='Installed requirements from {} into {}'.format(
            requirements_txt, self.remote_path))
Exemple #16
0
def enable_unit(unit_name, check_first=False):
    if check_first:
        state = get_unit_state(unit_name)
        # we use 'WantedBy' as a guess whether or not the service is enabled
        # when UnitFileState is not available (SysV init or older systemd)
        ufs = state.get('UnitFileState')
        if ufs == 'enabled' or ufs is None and 'WantedBy' in state:
            return Unchanged(msg='{} already enabled'.format(unit_name))

    proc.run([config['cmd_systemctl'], 'enable', unit_name])
    return Changed(msg='Enabled {}'.format(unit_name))
Exemple #17
0
def reboot():
    if config.get_bool('systemd'):
        try:
            proc.run([config['cmd_systemctl'], 'reboot'])
        except RemoteFailureError:
            # FIXME: should be more discerning; also verify reboot is taking
            #        place
            pass  # ignored, as the command will not finish - due to rebooting
    elif config['remote_os'] in ('unix', 'posix'):
        proc.run([config['cmd_shutdown'], '-r', 'now'])
    return Changed(msg='Server rebooting')
Exemple #18
0
def enable_unit(unit_name, check_first=False):
    if check_first:
        state = get_unit_state(unit_name)
        # we use 'WantedBy' as a guess whether or not the service is enabled
        # when UnitFileState is not available (SysV init or older systemd)
        ufs = state.get('UnitFileState')
        if ufs == 'enabled' or ufs is None and 'WantedBy' in state:
            return Unchanged(msg='{} already enabled'.format(unit_name))

    proc.run([config['cmd_systemctl'], 'enable', unit_name])
    return Changed(msg='Enabled {}'.format(unit_name))
Exemple #19
0
def reboot():
    if config.get_bool('systemd'):
        try:
            proc.run([config['cmd_systemctl'], 'reboot'])
        except RemoteFailureError:
            # FIXME: should be more discerning; also verify reboot is taking
            #        place
            pass  # ignored, as the command will not finish - due to rebooting
    elif config['remote_os'] in ('unix', 'posix'):
        proc.run([config['cmd_shutdown'], '-r', 'now'])
    return Changed(msg='Server rebooting')
Exemple #20
0
def dpkg_add_architecture(arch):
    archs = [info_dpkg_architecture()] + info_dpkg_foreign_architectures()

    if arch in archs:
        return Unchanged(msg='Architecture already enabled: {}'.format(arch))

    proc.run([config['cmd_dpkg'], '--add-architecture', arch])

    # invalidate caches
    info_dpkg_foreign_architectures.invalidate_cache()
    info_update_timestamp().mark_stale()
    return Changed(msg='New architecture added: {}'.format(arch))
Exemple #21
0
def dpkg_add_architecture(arch):
    archs = [info_dpkg_architecture()] + info_dpkg_foreign_architectures()

    if arch in archs:
        return Unchanged(msg='Architecture already enabled: {}'.format(arch))

    proc.run([config['cmd_dpkg'], '--add-architecture', arch])

    # invalidate caches
    info_dpkg_foreign_architectures.invalidate_cache()
    info_update_timestamp().mark_stale()
    return Changed(msg='New architecture added: {}'.format(arch))
Exemple #22
0
def expand_root_fs():
    dev_size, _, _ = proc.run(['fdisk', '-s', '/dev/mmcblk0'])
    p1_size, _, _ = proc.run(['fdisk', '-s', '/dev/mmcblk0p1'])
    p2_size, _, _ = proc.run(['fdisk', '-s', '/dev/mmcblk0p2'])
    free_space = (int(dev_size) - int(p1_size) - int(p2_size)) * 512

    if free_space <= 4 * 1024 * 1024:
        return Unchanged(
            msg='Free space is <= 4M. Not expanding root filesystem')
    else:
        # FIXME: run fdisk and resize2fs instead of raspi-config?
        proc.run(['raspi-config', '--expand-rootfs'])
        return Changed(msg='Expanded root filesystem')
Exemple #23
0
    def install(self, pkgs, upgrade=True, editable=False):
        # FIXME: collect changes

        args = [self.pip, 'install']
        if editable:
            args.append('-e')
        if upgrade:
            args.append('-U')
        args.extend(pkgs)

        proc.run(args)

        return Changed(msg='Installed packages {} into virtualenv {}'.format(
            pkgs, self.remote_path))
Exemple #24
0
def info_openssh_version():
    stdout, stderr, rval = proc.run(['sshd', '-?'], status_ok='any')

    if rval == 1:
        m = OPENSSH_VERSION_RE.match(stderr.splitlines()[1])
        if m:
            return (int(m.group(1)), int(m.group(2)), m.group(3))
Exemple #25
0
    def install(self, pkgs, upgrade=True, reinstall=False, editable=False):
        # FIXME: collect changes

        args = [self.pip, 'install']
        if editable:
            args.append('-e')
        if upgrade:
            args.append('-U')
        if reinstall:
            args.append('-I')
        args.extend(pkgs)

        proc.run(args)

        return Changed(msg='Installed packages {} into virtualenv {}'.format(
            pkgs, self.remote_path))
Exemple #26
0
def chown(remote_path, uid=None, gid=None, recursive=False):
    new_owner = ':'

    # no-op
    if uid is None and gid is None:
        return

    if uid is not None:
        new_owner = str(uid) + new_owner
    if gid is not None:
        new_owner += str(gid)

    cmd = [config['cmd_chown']]

    if recursive:
        cmd.append('-R')

    cmd.append('-c')  # FIXME: on BSDs, we need -v here?

    cmd.append(new_owner)
    cmd.append(remote_path)

    stdout, _, _ = proc.run(cmd)

    if stdout.strip():
        return Changed(msg='Changed ownership of {} to {}'
                       .format(remote_path, new_owner))

    return Unchanged(msg='Ownership of {} already {}'
                     .format(remote_path, new_owner))
Exemple #27
0
def info_openssh_version():
    stdout, stderr, rval = proc.run(['sshd', '-?'], status_ok='any')

    if rval == 1:
        m = OPENSSH_VERSION_RE.match(stderr.splitlines()[1])
        if m:
            return (int(m.group(1)), int(m.group(2)), m.group(3))
Exemple #28
0
def chown(remote_path, uid=None, gid=None, recursive=False):
    new_owner = ':'

    # no-op
    if uid is None and gid is None:
        return

    if uid is not None:
        new_owner = str(uid) + new_owner
    if gid is not None:
        new_owner += str(gid)

    cmd = [config['cmd_chown']]

    if recursive:
        cmd.append('-R')

    cmd.append('-c')  # FIXME: on BSDs, we need -v here?

    cmd.append(new_owner)
    cmd.append(remote_path)

    stdout, _, _ = proc.run(cmd)

    if stdout.strip():
        return Changed(msg='Changed ownership of {} to {}'.format(
            remote_path, new_owner))

    return Unchanged(msg='Ownership of {} already {}'.format(
        remote_path, new_owner))
Exemple #29
0
def install_strict_ssh(allow_users=['root'],
                       allow_groups=None,
                       address_family="any",
                       permit_root=True,
                       modern_ciphers=True,
                       sftp_enabled=True,
                       agent_forwarding=False,
                       x11=False,
                       tcp_forwarding=True,
                       unix_forwarding=True,
                       tunnel=False,
                       port=22,
                       use_dns=False,
                       print_motd=False,
                       auto_restart=True,
                       check_sshd_config=True,
                       password_enabled=None):
    # FIXME: change default in jinja templates to strict reporting of missing
    #        values to avoid creating broken ssh configs
    # FIXME: add (possibly generic) support for atomic-tested-configuration
    #        swaps (i.e. run sshd -t on a config)
    tpl = ssh_preset.templates.render(
        'sshd_config',
        allow_users=allow_users,
        allow_groups=allow_groups,
        address_family=address_family,
        permit_root=permit_root,
        modern_ciphers=modern_ciphers,
        sftp_enabled=sftp_enabled,
        agent_forwarding=agent_forwarding,
        x11=x11,
        tcp_forwarding=tcp_forwarding,
        unix_forwarding=unix_forwarding,
        tunnel=tunnel,
        ports=port if isinstance(port, list) else [port],
        print_motd=print_motd,
        password_enabled=password_enabled)

    if fs.upload_string(tpl, '/etc/ssh/sshd_config').changed:
        if check_sshd_config:
            proc.run(['sshd', '-t'])

        # FIXME: we may want to abstract the init-system here
        if auto_restart:
            systemd.restart_unit('ssh.service')
        return Changed(msg='Changed sshd configuration')
    return Unchanged(msg='sshd config already strict')
Exemple #30
0
def _get_lsb_info():
    stdout, stderr = proc.run([config['cmd_lsb_release'], '--all', '--short'])
    lines = stdout.splitlines()
    return {
        'dist_id': lines[0],
        'desc': lines[1],
        'release': lines[2],
        'codename': lines[3],
    }
Exemple #31
0
def install_strict_ssh(allow_users=['root'],
                       allow_groups=None,
                       address_family="any",
                       permit_root=True,
                       modern_ciphers=True,
                       sftp_enabled=True,
                       agent_forwarding=False,
                       x11=False,
                       tcp_forwarding=True,
                       unix_forwarding=True,
                       tunnel=False,
                       port=22,
                       use_dns=False,
                       print_motd=False,
                       auto_restart=True,
                       check_sshd_config=True):
    # FIXME: change default in jinja templates to strict reporting of missing
    #        values to avoid creating broken ssh configs
    # FIXME: add (possibly generic) support for atomic-tested-configuration
    #        swaps (i.e. run sshd -t on a config)
    tpl = ssh_preset.templates.render('sshd_config',
                                      allow_users=allow_users,
                                      allow_groups=allow_groups,
                                      address_family=address_family,
                                      permit_root=permit_root,
                                      modern_ciphers=modern_ciphers,
                                      sftp_enabled=sftp_enabled,
                                      agent_forwarding=agent_forwarding,
                                      x11=x11,
                                      tcp_forwarding=tcp_forwarding,
                                      unix_forwarding=unix_forwarding,
                                      tunnel=tunnel,
                                      port=port,
                                      print_motd=print_motd)

    if fs.upload_string(tpl, '/etc/ssh/sshd_config').changed:
        if check_sshd_config:
            proc.run(['sshd', '-t'])

        # FIXME: we may want to abstract the init-system here
        if auto_restart:
            systemd.restart_unit('ssh.service')
        return Changed(msg='Changed sshd configuration')
    return Unchanged(msg='sshd config already strict')
Exemple #32
0
def update(max_age=3600):
    if max_age < 0:
        return Unchanged(msg='apt update disabled (max_age < 0).')

    ts = info_update_timestamp()

    if max_age:
        age = ts.get_age()
        if age < max_age:
            return Unchanged(
                msg='apt cache is only {:.0f} minutes old, not updating'
                .format(age / 60))

    proc.run([config['cmd_apt_get'], 'update'])

    # modify update stamp
    ts.mark_current()

    return Changed(msg='apt cache updated')
Exemple #33
0
def update(max_age=3600):
    if max_age < 0:
        return Unchanged(msg='apt update disabled (max_age < 0).')

    ts = info_update_timestamp()

    if max_age:
        age = ts.get_age()
        if age < max_age:
            return Unchanged(
                msg='apt cache is only {:.0f} minutes old, not updating'
                .format(age / 60))

    proc.run([config['cmd_apt_get'], 'update'])

    # modify update stamp
    ts.mark_current()

    return Changed(msg='apt cache updated')
Exemple #34
0
def info_installed_packages():
    stdout, _, _ = proc.run([config['cmd_dpkg_query'], '--show'])

    pkgs = {}

    for line in stdout.splitlines():
        rec = PackageRecord(*line.split('\t'))
        pkgs[rec.name] = rec

    return pkgs
Exemple #35
0
def info_installed_packages():
    stdout, _, _ = proc.run([config['cmd_dpkg_query'], '--show'])

    pkgs = {}

    for line in stdout.splitlines():
        rec = PackageRecord(*line.split('\t'))
        pkgs[rec.name] = rec

    return pkgs
Exemple #36
0
def enable_module(module_name, load=True, modules_file="/etc/modules"):
    mods = info["linux.modules"]

    c = False

    # load module if not loaded
    if load and module_name not in mods:
        proc.run(["modprobe", module_name])
        c = True

    # ensure module is found in modules_files
    with fs.edit(modules_file) as mf:
        mf.insert_line(module_name)

    c |= mf.changed

    if c:
        return Changed(msg="Kernel module {}, enabled in {}".format(module_name, modules_file))

    return Unchanged(msg="Kernel module {} already enabled in {}".format(module_name, modules_file))
Exemple #37
0
def regenerate_host_keys(mark='/etc/ssh/host_keys_regenerated'):
    if mark:
        if remote.lstat(mark):
            return Unchanged(msg='Hostkeys have already been regenerated')

    key_names = [
        '/etc/ssh/ssh_host_ecdsa_key',
        '/etc/ssh/ssh_host_ed25519_key',
        '/etc/ssh/ssh_host_rsa_key',
        '/etc/ssh/ssh_host_dsa_key',
    ]

    def collect_fingerprints():
        fps = ''
        for key in key_names:
            if remote.lstat(key):
                fps += proc.run(['ssh-keygen', '-l', '-f', key])[0]
        return fps

    old_fps = collect_fingerprints()

    # remove old keys
    for key in key_names:
        fs.remove_file(key)
        fs.remove_file(key + '.pub')

    # generate new ones
    proc.run(['dpkg-reconfigure', 'openssh-server'])

    # restart openssh
    systemd.restart_unit('ssh.service')

    new_fps = collect_fingerprints()

    # mark host keys as new
    fs.touch(mark)

    return Changed(
        msg='Regenerated SSH host keys.\n'
        'Old fingerprints:\n{}\nNew fingerprints:\n{}\n'.format(
            util.indent('    ', old_fps), util.indent('    ', new_fps)))
Exemple #38
0
def regenerate_host_keys(mark='/etc/ssh/host_keys_regenerated'):
    if mark:
        if remote.lstat(mark):
            return Unchanged(msg='Hostkeys have already been regenerated')

    key_names = [
        '/etc/ssh/ssh_host_ecdsa_key',
        '/etc/ssh/ssh_host_ed25519_key',
        '/etc/ssh/ssh_host_rsa_key',
        '/etc/ssh/ssh_host_dsa_key',
    ]

    def collect_fingerprints():
        fps = ''
        for key in key_names:
            if remote.lstat(key):
                fps += proc.run(['ssh-keygen', '-l', '-f', key])[0]
        return fps

    old_fps = collect_fingerprints()

    # remove old keys
    for key in key_names:
        fs.remove_file(key)
        fs.remove_file(key + '.pub')

    # generate new ones
    proc.run(['dpkg-reconfigure', 'openssh-server'])

    # restart openssh
    systemd.restart_unit('ssh.service')

    new_fps = collect_fingerprints()

    # mark host keys as new
    fs.touch(mark)

    return Changed(
        msg='Regenerated SSH host keys.\n'
        'Old fingerprints:\n{}\nNew fingerprints:\n{}\n'.format(
            util.indent('    ', old_fps), util.indent('    ', new_fps)))
Exemple #39
0
    def create(self, python='python3', global_site_packages=False):
        # FIXME: check if python version and global_site_packages are correct,
        #        and correct / recreat (--clear?) otherwise

        if not remote.stat(self.python):
            args = [config['cmd_venv'], '-p', python, ]

            if global_site_packages:
                args.append('--system-site-packages')
            else:
                args.append('--no-site-packages')

            args.append(self.remote_path)

            proc.run(args)

            return Changed(
                msg='Initialized virtualenv in {}'.format(self.remote_path))

        return Unchanged(msg='virtualenv at {} already initialized'.format(
            self.remote_path))
Exemple #40
0
def useradd(name,
            groups=[],
            user_group=True,
            comment=None,
            home=None,
            create_home=None,
            system=False,
            shell=None):
    cmd = [config['cmd_useradd']]

    gs = groups[:]

    if gs:
        if not user_group:
            cmd.extend(('-g', gs.pop(0)))

        for g in gs:
            cmd.extend(('-G', g))

    if user_group is True:
        cmd.append('-U')
    elif user_group is False:
        cmd.append('-N')

    if comment is not None:
        cmd.extend(('-c', comment))

    if home is not None:
        cmd.extend(('-d', home))

    if create_home is False:
        cmd.append('-M')
    elif create_home is True:
        cmd.append('-m')

    if shell:
        cmd.extend(('-s', shell))

    if system:
        cmd.append('-r')

    cmd.append(name)

    stdout, stderr, returncode = proc.run(cmd,
                                          status_ok=(0, 9),
                                          status_meaning=_USERADD_STATUS_CODES)

    if returncode == 9:
        # FIXME: should check if user is up-to-date (home, etc)
        return Unchanged(msg='User {} already exists'.format(name))

    info_users.invalidate_cache()
    return Changed(msg='Created user {}'.format(name))
Exemple #41
0
def useradd(name,
            groups=[],
            user_group=True,
            comment=None,
            home=None,
            create_home=None,
            system=False,
            shell=None):
    cmd = [config['cmd_useradd']]

    gs = groups[:]

    if gs:
        if not user_group:
            cmd.extend(('-g', gs.pop(0)))

    if gs:
        cmd.extend(('-G', ','.join(groups)))

    if user_group is True:
        cmd.append('-U')
    elif user_group is False:
        cmd.append('-N')

    if comment is not None:
        cmd.extend(('-c', comment))

    if home is not None:
        cmd.extend(('-d', home))

    if create_home is False:
        cmd.append('-M')
    elif create_home is True:
        cmd.append('-m')

    if shell:
        cmd.extend(('-s', shell))

    if system:
        cmd.append('-r')

    cmd.append(name)

    stdout, stderr, returncode = proc.run(cmd,
                                          status_ok=(0, 9),
                                          status_meaning=_USERADD_STATUS_CODES)

    if returncode == 9:
        # FIXME: should check if user is up-to-date (home, etc)
        return Unchanged(msg='User {} already exists'.format(name))

    info_users.invalidate_cache()
    return Changed(msg='Created user {}'.format(name))
Exemple #42
0
def enable_module(module_name, load=True, modules_file='/etc/modules'):
    mods = info['linux.modules']

    c = False

    # load module if not loaded
    if load and module_name not in mods:
        proc.run(['modprobe', module_name])
        c = True

    # ensure module is found in modules_files
    with fs.edit(modules_file) as mf:
        mf.insert_line(module_name)

    c |= mf.changed

    if c:
        return Changed(msg='Kernel module {}, enabled in {}'.format(
            module_name, modules_file))

    return Unchanged(msg='Kernel module {} already enabled in {}'.format(
        module_name, modules_file))
Exemple #43
0
def add_apt_keys(key_filename, fingerprints=None):
    def get_fingerprints(buf):
        PREFIX = 'Key fingerprint = '

        fps = set()
        for line in buf.splitlines():
            line = line.strip()
            if line.startswith(PREFIX):
                fps.add(line[len(PREFIX):].replace(' ', ''))
        return fps

    def id_from_fingerprint(fp):
        return fp[-8:]

    if fingerprints is None:
        # first, we need to list all keys in the keyfile.
        # FIXME: allow use of remote gpg
        output = subprocess.check_output(
            ['gpg', '--with-fingerprint'], stdin=open(key_filename, 'r'))

        # FIXME: is utf8 the right call here?
        fingerprints = get_fingerprints(output.decode('utf8'))

    # check if key fingerprints are missing
    local_fps = fingerprints
    remote_fps = get_fingerprints(proc.run(['apt-key', 'fingerprints'])[0])
    missing_fps = local_fps.difference(remote_fps)

    if missing_fps:
        with open(key_filename, 'r') as k:
            proc.run(['apt-key', 'add', '-'], input=k)

        return Changed(
            msg='Added missing apt keys: {}'.format(', '.join(
                id_from_fingerprint(fp) for fp in sorted(missing_fps))))

    return Unchanged(
        msg='Apt keys {} already installed'.format(', '.join(
            id_from_fingerprint(fp) for fp in sorted(local_fps))))
Exemple #44
0
def upgrade(max_age=3600, force=False, dist_upgrade=False):
    # FIXME: should allow upgrading selected packages
    update(max_age)

    args = [config['cmd_apt_get']]

    # FIXME: check for upgrades first and output proper changed status
    args.extend([
        'upgrade' if not dist_upgrade else 'dist-upgrade',
        '--quiet',
        '--yes',
        # FIXME: options below don't work. why?
        # '--option', 'Dpkg::Options::="--force-confdef"',
        # '--option', 'Dpkg::Options::="--force-confold"'
    ])
    if force:
        args.append('--force-yes')
    proc.run(args, extra_env={'DEBIAN_FRONTEND': 'noninteractive', })

    info_installed_packages.invalidate_cache()

    return Changed(msg='Upgraded all packages')
Exemple #45
0
def set_hostname(hostname, domain=None, config_only=False):
    prev_hostname = info_hostname()

    changed = False
    changed |= fs.upload_string('{}\n'.format(hostname),
                                '/etc/hostname').changed

    if not config_only and prev_hostname != hostname:
        proc.run(['hostname', hostname])
        changed = True

    # update /etc/hosts
    # this will also set the domain name
    # see http://jblevins.org/log/hostname
    #
    # we adopt the following convention:

    host_line = '127.0.1.1\t' + hostname
    if domain:
        host_line = '127.0.1.1\t{}.{}\t{}'.format(hostname, domain, hostname)

    with fs.edit('/etc/hosts') as hosts:
        if host_line not in hosts.lines():
            hosts.comment_out(r'^127.0.1.1')

            # comment out old lines
            lines = [host_line] + hosts.lines()
            hosts.set_lines(lines)

    changed |= hosts.changed

    if changed:
        info_hostname.invalidate_cache()
        info_fqdn.invalidate_cache()
        return Changed(msg='Hostname changed from {} to {}'.format(
            prev_hostname, hostname))

    return Unchanged(msg='Hostname already set to {}'.format(hostname))
Exemple #46
0
def add_apt_keys(key_filename, fingerprints=None):
    def get_fingerprints(buf):
        PREFIX = 'Key fingerprint = '

        fps = set()
        for line in buf.splitlines():
            line = line.strip()
            if line.startswith(PREFIX):
                fps.add(line[len(PREFIX):].replace(' ', ''))
        return fps

    def id_from_fingerprint(fp):
        return fp[-8:]

    if fingerprints is None:
        # first, we need to list all keys in the keyfile.
        # FIXME: allow use of remote gpg
        output = subprocess.check_output(
            ['gpg', '--with-fingerprint'],
            stdin=open(key_filename, 'r'))

        # FIXME: is utf8 the right call here?
        fingerprints = get_fingerprints(output.decode('utf8'))

    # check if key fingerprints are missing
    local_fps = fingerprints
    remote_fps = get_fingerprints(proc.run(['apt-key', 'fingerprints'])[0])
    missing_fps = local_fps.difference(remote_fps)

    if missing_fps:
        with open(key_filename, 'r') as k:
            proc.run(['apt-key', 'add', '-'], input=k)

        return Changed(msg='Added missing apt keys: {}'.format(', '.join(
            id_from_fingerprint(fp) for fp in sorted(missing_fps))))

    return Unchanged(msg='Apt keys {} already installed'.format(', '.join(
        id_from_fingerprint(fp) for fp in sorted(local_fps))))
Exemple #47
0
def set_hostname(hostname, domain=None, config_only=False):
    prev_hostname = info_hostname()

    changed = False
    changed |= fs.upload_string('{}\n'.format(hostname),
                                '/etc/hostname').changed

    if not config_only and prev_hostname != hostname:
        proc.run(['hostname', hostname])
        changed = True

    # update /etc/hosts
    # this will also set the domain name
    # see http://jblevins.org/log/hostname
    #
    # we adopt the following convention:

    host_line = '127.0.1.1\t' + hostname
    if domain:
        host_line = '127.0.1.1\t{}.{}\t{}'.format(hostname, domain, hostname)

    with fs.edit('/etc/hosts') as hosts:
        if host_line not in hosts.lines():
            hosts.comment_out(r'^127.0.1.1')

            # comment out old lines
            lines = [host_line] + hosts.lines()
            hosts.set_lines(lines)

    changed |= hosts.changed

    if changed:
        info_hostname.invalidate_cache()
        info_fqdn.invalidate_cache()
        return Changed(msg='Hostname changed from {} to {}'.format(
            prev_hostname, hostname))

    return Unchanged(msg='Hostname already set to {}'.format(hostname))
Exemple #48
0
def remove_packages(pkgs, check_first=True, purge=False, max_age=3600):
    if check_first and not set(pkgs).intersection(set(info_installed_packages(
    ).keys())):
        return Unchanged(msg='Not installed: {}'.format(' '.join(pkgs)))

    update(max_age)

    args = [config['cmd_apt_get']]

    args.extend([
        'remove' if not purge else 'purge',
        '--quiet',
        '--yes',  # FIXME: options below don't work. why?
        # '--option', 'Dpkg::Options::="--force-confdef"',
        # '--option', 'Dpkg::Options::="--force-confold"'
    ])
    args.extend(pkgs)
    proc.run(args, extra_env={'DEBIAN_FRONTEND': 'noninteractive', })

    info_installed_packages.invalidate_cache()

    return Changed(msg='{} {}'.format('Removed' if not purge else 'Purged',
                                      ' '.join(pkgs)))
Exemple #49
0
def install_packages(pkgs,
                     check_first=True,
                     release=None,
                     max_age=3600,
                     force=False):

    # check if packages are already installed, ignoring version and arch
    if check_first and set(pkgs) < set(
            k.rsplit(':', 1)[0] for k in info_installed_packages().keys()):
        return Unchanged(msg='Already installed: {}'.format(' '.join(pkgs)))

    update(max_age)

    args = [config['cmd_apt_get']]
    if release:
        args.extend(['-t', release])

    args.extend([
        'install',
        '--quiet',
        '--yes',  # FIXME: options below don't work. why?
        # '--option', 'Dpkg::Options::="--force-confdef"',
        # '--option', 'Dpkg::Options::="--force-confold"'
    ])
    if force:
        args.append('--force-yes')
    args.extend(pkgs)
    proc.run(
        args, extra_env={
            'DEBIAN_FRONTEND': 'noninteractive',
        })

    # FIXME: make this a decorator for info, add "change_invalides" decorator?
    info_installed_packages.invalidate_cache()

    # FIXME: detect if packages were installed?
    return Changed(msg='Installed {}'.format(' '.join(pkgs)))
Exemple #50
0
def auto_remove(max_age=3600):
    update(max_age)  # FIXME: make max_age become a config setting, add a
    #        with_config context manager

    args = [config['cmd_apt_get']]
    args.extend(['autoremove', '--quiet', '--yes', ])
    stdout, _, _ = proc.run(args,
                            extra_env={'DEBIAN_FRONTEND': 'noninteractive', })

    if '0 to remove' in stdout:
        return Unchanged(msg='No packages auto-removed')

    info_installed_packages.invalidate_cache()

    return Changed(msg='Some packages were auto-removed')
Exemple #51
0
def info_system():
    FLAG_LIST = {
        'machine': '-m',
        'nodename': '-n',
        'kernel_name': '-s',
        'kernel_release': '-r',
        'kernel_version': '-v',
        'processor': '-p',
    }

    flag_values = {}
    for flag_name, flag in FLAG_LIST.items():
        out, _, _ = proc.run([config['cmd_uname'], flag])
        flag_values[flag_name] = out.rstrip()

    return flag_values
Exemple #52
0
def info_system():
    FLAG_LIST = {
        'machine': '-m',
        'nodename': '-n',
        'kernel_name': '-s',
        'kernel_release': '-r',
        'kernel_version': '-v',
        'processor': '-p',
    }

    flag_values = {}
    for flag_name, flag in FLAG_LIST.items():
        out, _, _ = proc.run([config['cmd_uname'], flag])
        flag_values[flag_name] = out.rstrip()

    return flag_values
Exemple #53
0
def query_cache(pkgs):
    stdout, _, _ = proc.run([config['cmd_apt_cache'], 'show'] + list(pkgs))
    pkgs = OrderedDict()
    for dump in stdout.split('\n\n'):
        # skip empty lines
        if not dump or dump.isspace():
            continue
        try:
            pkg_info = Deb822(dump)
        except ValueError:
            log.debug(dump)
            raise RemoteFailureError('Error parsing Deb822 info.')

        pkgs[pkg_info['Package']] = pkg_info

    return Unchanged(pkgs)
Exemple #54
0
def query_cache(pkgs):
    stdout, _, _ = proc.run([config['cmd_apt_cache'], 'show'] + list(pkgs))
    pkgs = OrderedDict()
    for dump in stdout.split('\n\n'):
        # skip empty lines
        if not dump or dump.isspace():
            continue
        try:
            pkg_info = Deb822(dump)
        except ValueError:
            log.debug(dump)
            raise RemoteFailureError('Error parsing Deb822 info.')

        pkgs[pkg_info['Package']] = pkg_info

    return Unchanged(pkgs)
Exemple #55
0
def userdel(name, remove_home=False, force=False):
    cmd = [config['cmd_userdel']]

    if remove_home:
        cmd.append('-r')

    if force:
        cmd.append('-f')

    cmd.append(name)

    stdout, stderr, returncode = proc.run(cmd,
                                          status_ok=(0, 6),
                                          status_meaning=_USERDEL_STATUS_CODES)

    if returncode == 6:
        return Unchanged(msg='User {} does not exist'.format(name))

    info_users.invalidate_cache()
    return Changed(msg='Removed user {}'.format(name))
Exemple #56
0
def userdel(name, remove_home=False, force=False):
    cmd = [config['cmd_userdel']]

    if remove_home:
        cmd.append('-r')

    if force:
        cmd.append('-f')

    cmd.append(name)

    stdout, stderr, returncode = proc.run(cmd,
                                          status_ok=(0, 6),
                                          status_meaning=_USERDEL_STATUS_CODES)

    if returncode == 6:
        return Unchanged(msg='User {} does not exist'.format(name))

    info_users.invalidate_cache()
    return Changed(msg='Removed user {}'.format(name))
Exemple #57
0
def info_timedatestatus():
    stdout, _, _ = proc.run(['timedatectl', 'status'])

    vals = {}
    for line in stdout.splitlines():
        line = line.strip()
        if not line:
            continue

        k, v = line.strip().split(':', 1)
        k = k.strip()
        v = v.strip()

        if v == 'yes':
            v = True
        if v == 'no':
            v = False
        vals[k] = v

    return vals
Exemple #58
0
def auto_remove(max_age=3600):
    update(max_age)  # FIXME: make max_age become a config setting, add a
    #        with_config context manager

    args = [config['cmd_apt_get']]
    args.extend([
        'autoremove',
        '--quiet',
        '--yes',
    ])
    stdout, _, _ = proc.run(
        args, extra_env={
            'DEBIAN_FRONTEND': 'noninteractive',
        })

    if '0 to remove' in stdout:
        return Unchanged(msg='No packages auto-removed')

    info_installed_packages.invalidate_cache()

    return Changed(msg='Some packages were auto-removed')
Exemple #59
0
def remote_tmpdir(delete=True, randbytes=16, mode=0o700):
    # FIXME: audit this for security issues

    if config['cmd_mktemp']:
        # create directory using mktemp command
        tmpdir, _, _ = proc.run([config['cmd_mktemp'], '-d'])
        tmpdir = tmpdir.rstrip('\n')
    else:
        # emulate mktemp
        tmpdir = remote.path.join(config['fs_fallback_tmpdir'],
                                  'remand-' + hexlify(os.urandom(randbytes)))

        remote.mkdir(tmpdir, mode=mode)

    log.debug('Created temporary directory {}'.format(tmpdir))

    try:
        yield tmpdir
    finally:
        if delete:
            log.debug('Removing temporary directory {}'.format(tmpdir))
            remove_dir(tmpdir)