def deselect_handler(args):
    selectors = get_server_setting(selectors_setting)
    if isinstance(selectors, str):
        selectors = [selectors]
        set_server_setting(selectors_setting, selectors)
    if selectors is None:
        selectors = []
        set_server_setting(selectors_setting, selectors)

    errors = False
    for selector in args.selector:
        try:
            selectors.remove(selector)
        except ValueError:
            sys.stderr.write(
                'Selector {} is not currently selected.\n'.format(selector))
            errors = True
    if errors:
        sys.exit(1)

    for selector in args.selector:
        log.info('Removing secret-keeping selector {}', selector)

    save_server_settings()

    set_client_setting(selectors_setting, selectors)
    save_client_settings()

    print(restart_note)
def disable_handler(args):
    if not get_server_setting('secret_keeping:enabled'):
        sys.exit('Secret-keeping is not enabled.')

    set_server_setting('secret_keeping:enabled', False)
    save_server_settings()

    set_client_setting('secret_keeping:enabled', False)
    save_client_settings()

    print(restart_note)

    log.info('Disabled secret-keeping')
Beispiel #3
0
def remove_port(args):
    port = args.port
    ports = get_server_setting('port')
    if not ports:
        sys.exit('There are no ports to remove!')
    if isinstance(ports, int) or len(ports) == 1:
        sys.exit("You can't remove the only configured port!")
    if port not in ports:
        sys.exit('Port {} is not currently configured'.format(port))
    if isinstance(ports, list):
        ports.remove(port)
    else:
        ports.pop(port)
    set_server_setting('port', ports)
    save_server_settings()
    print('Removed port {}.'.format(port))
    show_configuration(args)
def select_handler(args):
    selectors = get_server_setting(selectors_setting)
    if isinstance(selectors, str):
        selectors = [selectors]
        set_server_setting(selectors_setting, selectors)
    if selectors is None:
        selectors = []
        set_server_setting(selectors_setting, selectors)

    errors = False
    for selector in args.selector:
        if selector in selectors:
            sys.stderr.write(
                'Selector {} is already added.\n'.format(selector))
            errors = True
    if errors:
        sys.exit(1)

    if not args.force:
        db = get_db()
        for selector in args.selector:
            if not db.clients.find_one({selector: {
                    '$exists': True
            }},
                                       projection=[]):
                sys.stderr.write(
                    'Selector {} does not match anything.\n'
                    'Specify --force to save anyway.\n'.format(selector))
                errors = True
        if errors:
            sys.exit(1)

    for selector in args.selector:
        log.info('Adding secret-keeping selector {}', selector)

    selectors.extend(args.selector)
    save_server_settings()

    set_client_setting(selectors_setting, selectors)
    save_client_settings()

    print(restart_note)
Beispiel #5
0
def main(args):
    if args.yes:
        maybe_changed = partial(maybe_changed_extended, use_default=True)
    else:
        maybe_changed = maybe_changed_extended

    generate_key('server', gpg_user_ids['server'])
    generate_key('client', gpg_user_ids['client'])
    import_key('server', gpg_user_ids['client'])
    import_key('client', gpg_user_ids['server'])

    default = not (get_client_setting('loaded')
                   and get_server_setting('loaded'))

    do_config = default if args.yes else \
        get_bool('Do you want to configure things interactively?', default)

    server_changed = client_changed = False

    if do_config:
        if isinstance(get_server_setting('port'), int):
            # Otherwise, the settings file has been edited to make the port
            # either a list of ports or a mapping, and we don't want to try to
            # configure it here.
            server_changed |= maybe_changed(
                'server', 'port', get_int,
                'What port should the server listen on?')

        server_changed |= maybe_changed(
            'server', 'local_port', get_int,
            'What local-only port should the server use?')

        configure_ssl = True
        port = get_server_setting('port')
        if isinstance(port, dict):
            for port_number, port_settings in port.items():
                if 'ssl' in port_settings:
                    # If there are already port-specific SSL settings, then
                    # don't try to configure SSL in this script.
                    configure_ssl = False
        if configure_ssl:
            default = bool(
                get_server_setting('ssl:certificate', None)
                or get_server_setting('ssl:key', None))
            configure_ssl = maybe_get_bool(
                'Do you want the server to use SSL?', default, args.yes)
        if not configure_ssl:
            if get_server_setting('ssl:certificate', None):
                set_server_setting('ssl:certificate', None)
                server_changed = True
            if get_server_setting('ssl:key', None):
                set_server_setting('ssl:key', None)
                server_changed = True
        else:
            while True:
                server_changed |= maybe_changed('server', 'ssl:certificate',
                                                get_string,
                                                'SSL certificate file path:')
                if os.path.exists(get_server_setting('ssl:certificate')):
                    break
                print('That file does not exist.')

            while True:
                server_changed |= maybe_changed('server', 'ssl:key',
                                                get_string,
                                                'SSL key file path:')
                if os.path.exists(get_server_setting('ssl:key')):
                    break
                print('That file does not exist.')

        server_changed |= maybe_changed('server', 'database:host',
                                        get_string_or_list,
                                        'Database host:port:')
        if get_server_setting('database:host'):
            server_changed |= maybe_changed('server',
                                            'database:replicaset',
                                            get_string_none,
                                            'Replicaset name:',
                                            empty_ok=True)
        server_changed |= maybe_changed('server', 'database:name', get_string,
                                        'Database name:')
        server_changed |= maybe_changed('server',
                                        'database:username',
                                        get_string_none,
                                        'Database username:'******'database:username'):
            server_changed |= maybe_changed('server', 'database:password',
                                            get_string, 'Database password:'******'Server', maybe_changed)
        server_changed |= maybe_changed(
            'server', 'audit_cron:enabled', get_bool,
            'Do you want to enable the audit cron job?')

        if get_server_setting('audit_cron:enabled'):
            server_changed |= maybe_changed(
                'server', 'audit_cron:email', get_string,
                'What email address should get the audit output?')

        port = get_server_setting('port')
        if port == 443:
            sample_url = 'https://hostname'
        elif port == 80:
            sample_url = 'http://hostname'
        else:
            sample_url = 'http://hostname:{}'.format(port)
        prompt = 'URL base, e.g., {}, for clients to reach server:'.format(
            sample_url)

        client_changed |= maybe_changed('client', 'server_url', get_string,
                                        prompt)

        client_changed |= maybe_changed('client',
                                        'geolocation_api_key',
                                        get_string,
                                        'Google geolocation API key, if any:',
                                        empty_ok=True)
        prompter = partial(get_int, minimum=1)
        client_changed |= maybe_changed(
            'client', 'schedule:collect_interval', prompter,
            'How often (minutes) do you want to collect data?')
        client_changed |= maybe_changed(
            'client', 'schedule:submit_interval', prompter,
            'How often (minutes) do you want re-try submits?')

        client_changed |= configure_logging('Client', maybe_changed)

        save_server_settings()
        if server_changed:
            print('Saved server settings.')

        save_client_settings()
        if client_changed:
            print('Saved client settings.')

    service_file = '/etc/systemd/system/penguindome-server.service'
    service_exists = os.path.exists(service_file)
    default = not service_exists

    if service_exists:
        prompt = ("Do you want to replace the server's systemd "
                  "configuration?")
    else:
        prompt = 'Do you want to add the server to systemd?'

    do_service = maybe_get_bool(prompt, default, args.yes)

    if do_service and not args.skipsystemctl:
        with NamedTemporaryFile('w+') as temp_service_file:
            temp_service_file.write(
                dedent('''\
                [Unit]
                Description=PenguinDome Server
                After=network.target

                [Service]
                Type=simple
                ExecStart={server_exe}

                [Install]
                WantedBy=multi-user.target
            '''.format(server_exe=os.path.join(top_dir, 'bin', 'server'))))
            temp_service_file.flush()
            os.chmod(temp_service_file.name, 0o644)
            shutil.copy(temp_service_file.name, service_file)
        subprocess.check_output(('systemctl', 'daemon-reload'),
                                stderr=subprocess.STDOUT)
        service_exists = True

    if service_exists and not args.skipsystemctl:
        try:
            subprocess.check_output(
                ('systemctl', 'is-enabled', 'penguindome-server'),
                stderr=subprocess.STDOUT)
        except Exception:
            if maybe_get_bool('Do you want to enable the server?', True,
                              args.yes):
                subprocess.check_output(
                    ('systemctl', 'enable', 'penguindome-server'),
                    stderr=subprocess.STDOUT)
                is_enabled = True
            else:
                is_enabled = False
        else:
            is_enabled = True

        if is_enabled:
            try:
                subprocess.check_output(
                    ('systemctl', 'status', 'penguindome-server'),
                    stderr=subprocess.STDOUT)
            except Exception:
                if maybe_get_bool('Do you want to start the server?', True,
                                  args.yes):
                    subprocess.check_output(
                        ('systemctl', 'start', 'penguindome-server'),
                        stderr=subprocess.STDOUT)
            else:
                if maybe_get_bool('Do you want to restart the server?',
                                  server_changed, args.yes):
                    subprocess.check_output(
                        ('systemctl', 'restart', 'penguindome-server'),
                        stderr=subprocess.STDOUT)

        if get_server_setting('audit_cron:enabled'):
            cron_file = '/etc/cron.d/penguindome-audit'
            cron_exists = os.path.exists(cron_file)

            if cron_exists:
                prompt = 'Do you want to replace the audit crontab?'
            else:
                prompt = 'Do you want to install the audit crontab?'

            do_crontab = maybe_get_bool(prompt, args.audit_crontab
                                        or not cron_exists, args.audit_crontab
                                        or args.yes)

            if do_crontab:
                email = get_server_setting('audit_cron:email')
                minute = int(random.random() * 60)
                minute2 = (minute + 1) % 60

                crontab = dedent('''\
                    MAILTO={email}
                    {minute2} * * * * root "{top_dir}/bin/issues" audit --cron
                '''.format(minute2=minute2, email=email, top_dir=top_dir))

                with NamedTemporaryFile('w+') as temp_cron_file:
                    temp_cron_file.write(crontab)
                    temp_cron_file.flush()
                    os.chmod(temp_cron_file.name, 0o644)
                    shutil.copy(temp_cron_file.name, cron_file)

                print('Installed {}'.format(cron_file))

    if (client_changed
            or not glob.glob(os.path.join(releases_dir, '*.tar.asc'))
            and not args.skipbuildrelease):
        if client_changed:
            prompt = ('Do you want to build a release with the new client '
                      'settings?')
        else:
            prompt = 'Do you want to build a client release?'

        if maybe_get_bool(prompt, True, args.yes):
            # Sometimes sign fails the first time because of GnuPG weirdness.
            # The client_release script will call sign as well, but we call it
            # first just in case it fails the first time.
            try:
                subprocess.check_output((os.path.join('bin', 'sign'), ),
                                        stderr=subprocess.STDOUT)
            except Exception:
                pass
            subprocess.check_output((os.path.join('bin', 'client_release'), ),
                                    stderr=subprocess.STDOUT)

    print('Done!')
def enable_handler(args):
    if get_server_setting('secret_keeping:enabled'):
        sys.exit('Secret-keeping is already enabled.')

    if get_server_setting('secret_keeping:key_id'):
        if not (args.replace or args.preserve):
            sys.exit('Must specify --replace or --preserve.')
    else:
        args.replace = True

    args.shares = args.shares or \
        get_server_setting('secret_keeping:num_shares')
    if args.shares < 2:
        sys.exit('--num-shares must be at least 2.')

    args.combine_threshold = args.combine_threshold or \
        get_server_setting('secret_keeping:combine_threshold')
    if args.combine_threshold < 2:
        sys.exit('--combine-threshold must be at least 2.')
    if args.combine_threshold > args.shares:
        sys.exit(
            '--combine-threshold must be less than {}.'.format(args.shares +
                                                               1))

    if args.replace:
        key_name = 'penguindome-secret-keeping-' + uuid.uuid4().hex
        output = gpg_command('--passphrase',
                             '',
                             '--quick-gen-key',
                             key_name,
                             with_trustdb=True,
                             quiet=False)
        match = re.search(r'key (.*) marked as ultimately trusted', output)
        key_id = match.group(1)
        match = re.search(r'/([0-9A-F]+)\.rev', output)
        key_fingerprint = match.group(1)

        split_dir = os.path.join(var_dir, key_name)
        key_file = os.path.join(split_dir, 'private_key.asc')
        os.makedirs(split_dir)
        gpg_command('--export-secret-key', '--armor', '-o', key_file, key_id)
        subprocess.check_output(('gfsplit', '-n', str(
            args.combine_threshold), '-m', str(args.shares), key_file),
                                stderr=subprocess.STDOUT)
        try:
            gpg_command('--delete-secret-keys', key_fingerprint)
        except subprocess.CalledProcessError as e:
            sys.exit('Failed to delete secret key:\n{}'.format(
                e.output.decode('utf8')))
        subprocess.check_output(('shred', '-u', key_file),
                                stderr=subprocess.STDOUT)

        with NamedTemporaryFile() as public_key_file:
            gpg_command('--export', '-o', public_key_file.name, key_id)
            set_gpg('client')
            try:
                gpg_command('--import', public_key_file.name)
            finally:
                set_gpg('server')

        set_server_setting('secret_keeping:key_name', key_name)
        set_server_setting('secret_keeping:key_id', key_id)
        set_server_setting('secret_keeping:key_fingerprint', key_fingerprint)

        set_client_setting('secret_keeping:key_id', key_id)

    set_server_setting('secret_keeping:num_shares', args.shares)
    set_server_setting('secret_keeping:combine_threshold',
                       args.combine_threshold)
    set_server_setting('secret_keeping:enabled', True)
    save_server_settings()

    set_client_setting('secret_keeping:enabled', True)
    save_client_settings()

    print(
        distribute_secrets_note.format(m=args.shares,
                                       n=args.combine_threshold,
                                       split_dir=split_dir))
    print(restart_note)

    log.info('Enabled secret-keeping')
Beispiel #7
0
def configure_port(args, add=False):
    if args.ssl_self_signed and (args.certificate or args.key):
        sys.exit('--certificate and --key are incompatible with '
                 '--ssl-self-signed.')

    changed = False
    port = args.port
    ports = get_server_setting('port', None)
    if not ports:
        ports = []
    elif isinstance(ports, int):
        ports = [ports]
    if isinstance(ports, list):
        ports = {port: {} for port in ports}
        set_server_setting('port', ports)

    if port in ports:
        if add:
            sys.exit('Port {} is already present.'.format(args.port))
        which = 'Configured'
    else:
        ports[port] = {}
        changed = True
        which = 'Added'

    if not ports[port]:
        ports[port] = {}
    port_settings = ports[port]
    gps = partial(get_port_setting, port)

    def ss(setting, value):
        nonlocal changed
        set_setting(port_settings, setting, value)
        changed = True

    if args.deprecated is not None:
        if bool(gps('deprecated')) != args.deprecated:
            ss('deprecated', args.deprecated)

    if args.ssl_self_signed:
        cert_file, key_file = make_self_signed_cert(args.ssl_self_signed)
        ss('ssl:certificate', cert_file)
        ss('ssl:key', key_file)
        if args.ssl is not False:
            args.ssl = True

    if args.certificate and gps('ssl:certificate') != args.certificate:
        if not (args.key or gps('ssl:key')):
            sys.exit('You must specify both a certificate and a key.')
        if not os.path.exists(args.certificate):
            sys.exit('The certificate file {} does not exist'.format(
                args.certificate))
        ss('ssl:certificate', args.certificate)

    if args.key and gps('ssl:key') != args.key:
        if not gps('ssl:certificate'):
            sys.exit('You must specify both a certificate and a key.')
        if not os.path.exists(args.key):
            sys.exit('The key file {} does not exist'.format(
                args.key))
        ss('ssl:key', args.key)

    if args.ssl:
        if not (gps('ssl:certificate') and gps('ssl:key')):
            sys.exit('You must specify a certificate and key to enable SSL.')
        if not gps('ssl:enabled', bool(gps('ssl:certificate'))):
            ss('ssl:enabled', True)
    elif args.ssl is False:
        if gps('ssl:enabled', bool(gps('ssl:certificate'))):
            ss('ssl:enabled', False)

    if changed:
        save_server_settings()

        url = get_client_setting('server_url')
        if url:
            url = urlparse(url)
            if ':' in url[1]:
                _, client_port = url[1].split(':')
                client_port = int(client_port)
            else:
                client_port = {'http': 80, 'https': 443}[url[0]]
            client_ssl = {'http': False, 'https': True}[url[0]]
            if port == client_port:
                if client_ssl != gps(
                        'ssl:enabled', bool(gps('ssl:certificate'))):
                    print('\n'
                          'WARNING: Client is configured to use port {} and {}'
                          'using SSL.\n'
                          '         Should it be?\n'.format(
                              port, '' if client_ssl else 'not '))
                if gps('deprecated'):
                    print('\n'
                          'WARNING: Client is configured to use deprecated '
                          'port {}.\n'
                          '         Do you need to change the client port?'.
                          format(port))

        print('{} port {}.'.format(which, port))

        print("\n"
              "WARNING: Don't forget to restart the server.\n")

        if args.ssl_self_signed and args.ssl:
            print("\n"
                  "WARNING: Don't forget to configure client CA file\n"
                  "         (see help for 'configure-client').\n")

        show_configuration(args)
    else:
        print('No changes.')